benchlab-pytools 3.3.0__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.
Files changed (224) hide show
  1. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/CHANGELOG.md +35 -0
  2. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/PKG-INFO +22 -11
  3. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/README.md +16 -0
  4. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/__init__.py +1 -1
  5. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/core/datasource.py +13 -0
  6. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/flash/README.md +20 -4
  7. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/flash/flash_manager.py +24 -9
  8. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/flash/flash_tool.py +27 -4
  9. benchlab_pytools-3.4.0/benchlab/flash/requirements.txt +11 -0
  10. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/flash/test_flash_manager.py +43 -5
  11. benchlab_pytools-3.4.0/benchlab/link/requirements.txt +3 -0
  12. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/main.py +24 -8
  13. benchlab_pytools-3.4.0/benchlab/mqtt/requirements.txt +3 -0
  14. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/tools.py +3 -1
  15. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/tui/README.md +7 -2
  16. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/tui/config.py +14 -1
  17. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/tui/test_tui_core.py +53 -4
  18. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/tui/tui_core.py +111 -2
  19. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/pyproject.toml +19 -7
  20. benchlab_pytools-3.3.0/benchlab/flash/requirements.txt +0 -7
  21. benchlab_pytools-3.3.0/benchlab/mqtt/requirements.txt +0 -3
  22. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/.flake8 +0 -0
  23. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/.gitignore +0 -0
  24. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/CONTRIBUTING.md +0 -0
  25. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/LICENSE +0 -0
  26. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/__main__.py +0 -0
  27. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/bootstrap.py +0 -0
  28. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/config/README.md +0 -0
  29. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/config/__init__.py +0 -0
  30. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/config/config_client.py +0 -0
  31. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/config/config_manager.py +0 -0
  32. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/config/config_tool.py +0 -0
  33. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/config/diff.py +0 -0
  34. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/config/examples/complete_config.json +0 -0
  35. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/config/examples/rgb_lighting.json +0 -0
  36. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/config/examples/simple_fan.json +0 -0
  37. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/config/http_dto.py +0 -0
  38. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/config/schema.py +0 -0
  39. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/config/test_config_client.py +0 -0
  40. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/config/test_config_manager.py +0 -0
  41. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/config/test_diff.py +0 -0
  42. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/config/test_http_config_client.py +0 -0
  43. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/config/test_http_dto.py +0 -0
  44. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/core/README.md +0 -0
  45. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/core/__init__.py +0 -0
  46. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/core/config.py +0 -0
  47. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/core/datasource_manager.py +0 -0
  48. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/core/device_registry.py +0 -0
  49. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/core/discovery.py +0 -0
  50. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/core/infrastructure.py +0 -0
  51. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/core/process_manager.py +0 -0
  52. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/core/retry.py +0 -0
  53. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/core/shared_serial.py +0 -0
  54. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/core/statistics.py +0 -0
  55. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/csv_log/README.md +0 -0
  56. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/csv_log/__init__.py +0 -0
  57. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/csv_log/csv_logger_enhanced.py +0 -0
  58. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/csv_log/message_batcher.py +0 -0
  59. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/csv_log/requirements.txt +0 -0
  60. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/csv_log/smart_retry.py +0 -0
  61. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/flash/__init__.py +0 -0
  62. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/flash/dfu.py +0 -0
  63. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/flash/image.py +0 -0
  64. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/flash/test_dfu.py +0 -0
  65. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/flash/test_flash_tool.py +0 -0
  66. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/flash/test_image.py +0 -0
  67. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/graph/README.md +0 -0
  68. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/graph/__init__.py +0 -0
  69. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/graph/app.py +0 -0
  70. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/graph/requirements.txt +0 -0
  71. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/graph/runner.py +0 -0
  72. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/graph/test_graph.py +0 -0
  73. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/graph/ui.py +0 -0
  74. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/hwinfo/README.md +0 -0
  75. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/hwinfo/__init__.py +0 -0
  76. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/hwinfo/hwinfo_export.py +0 -0
  77. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/hwinfo/requirements.txt +0 -0
  78. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/hwinfo/test_hwinfo_export.py +0 -0
  79. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/launcher.py +0 -0
  80. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/link/.env.EXAMPLE +0 -0
  81. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/link/README.md +0 -0
  82. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/link/__init__.py +0 -0
  83. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/link/link_main.py +0 -0
  84. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/link/test_link_main.py +0 -0
  85. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/menu.py +0 -0
  86. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/menu_classic.py +0 -0
  87. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/mqtt/README.md +0 -0
  88. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/mqtt/__init__.py +0 -0
  89. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/mqtt/mqtt.config_template +0 -0
  90. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/mqtt/mqtt_publisher.py +0 -0
  91. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/mqtt/test_mqtt_publisher.py +0 -0
  92. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/prefs.py +0 -0
  93. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/requirements.txt +0 -0
  94. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/restapi/.env.example +0 -0
  95. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/restapi/__init__.py +0 -0
  96. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/restapi/favicon.ico +0 -0
  97. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/restapi/readme.md +0 -0
  98. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/restapi/requirements.txt +0 -0
  99. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/restapi/telemetry_api.py +0 -0
  100. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/restapi/test_read_recovery.py +0 -0
  101. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/restapi/test_server.py +0 -0
  102. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/sources.py +0 -0
  103. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/tui/__init__.py +0 -0
  104. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/tui/requirements.txt +0 -0
  105. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/tui/test_tui_main.py +0 -0
  106. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/tui/tui_main.py +0 -0
  107. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/README.md +0 -0
  108. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/NOTICE.md +0 -0
  109. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/README.md +0 -0
  110. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/Running_from_source_on_linux.md +0 -0
  111. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/__init__.py +0 -0
  112. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/assets/vu1_hello_world.png +0 -0
  113. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/community_applications.md +0 -0
  114. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/config.yaml +0 -0
  115. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/database.py +0 -0
  116. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/dial_driver.py +0 -0
  117. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/dials/Comms_Hub_Gauge.py +0 -0
  118. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/dials/Comms_Hub_Server.py +0 -0
  119. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/dials/__init__.py +0 -0
  120. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/dials/base_logger.py +0 -0
  121. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/installer/inc/icon.ico +0 -0
  122. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/installer/install.nsi +0 -0
  123. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/make_version.py +0 -0
  124. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/pylintrc +0 -0
  125. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/requirements.txt +0 -0
  126. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/serial_driver.py +0 -0
  127. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/server.py +0 -0
  128. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/server_config.py +0 -0
  129. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/server_dial_handler.py +0 -0
  130. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/vu_notifications.py +0 -0
  131. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/android-chrome-192x192.png +0 -0
  132. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/android-chrome-512x512.png +0 -0
  133. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/apple-touch-icon.png +0 -0
  134. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/demo.css +0 -0
  135. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/demo.min.css +0 -0
  136. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/demo.rtl.css +0 -0
  137. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/demo.rtl.min.css +0 -0
  138. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.css +0 -0
  139. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.min.css +0 -0
  140. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.css +0 -0
  141. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.min.css +0 -0
  142. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.css +0 -0
  143. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.min.css +0 -0
  144. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.css +0 -0
  145. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.min.css +0 -0
  146. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.css +0 -0
  147. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.min.css +0 -0
  148. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.css +0 -0
  149. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.min.css +0 -0
  150. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.css +0 -0
  151. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.min.css +0 -0
  152. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.css +0 -0
  153. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.min.css +0 -0
  154. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler.css +0 -0
  155. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler.min.css +0 -0
  156. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler.rtl.css +0 -0
  157. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/tabler.rtl.min.css +0 -0
  158. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/css/theme.min.css +0 -0
  159. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/img/header.png +0 -0
  160. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/img/logo.png +0 -0
  161. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/dark-theme.js +0 -0
  162. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/dark-theme.min.js +0 -0
  163. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/demo.js +0 -0
  164. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/demo.min.js +0 -0
  165. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/jquery.min.js +0 -0
  166. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/tabler.esm.js +0 -0
  167. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/tabler.esm.min.js +0 -0
  168. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/tabler.js +0 -0
  169. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/tabler.min.js +0 -0
  170. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/vu1_api.js +0 -0
  171. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_key_settings.js +0 -0
  172. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_keys.js +0 -0
  173. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_dial.js +0 -0
  174. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_home.js +0 -0
  175. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_root.js +0 -0
  176. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/favicon-16x16.png +0 -0
  177. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/favicon-32x32.png +0 -0
  178. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/favicon.ico +0 -0
  179. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/favicon.png +0 -0
  180. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/index.html +0 -0
  181. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/views/api_keys.html +0 -0
  182. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/views/dial.html +0 -0
  183. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/views/footer.html +0 -0
  184. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/views/header.html +0 -0
  185. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/views/key_settings.html +0 -0
  186. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/VU-Server/www/views/start.html +0 -0
  187. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/__init__.py +0 -0
  188. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/assets/Barlow-Bold.ttf +0 -0
  189. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/assets/bl_dial_144x200.png +0 -0
  190. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/assets/bl_logo_144x200.png +0 -0
  191. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/devices.py +0 -0
  192. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/requirements.txt +0 -0
  193. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/sensors.py +0 -0
  194. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/test_devices.py +0 -0
  195. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/test_vu_server_config.py +0 -0
  196. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/test_vu_server_manager.py +0 -0
  197. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/test_vu_tui.py +0 -0
  198. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/vu_dial.config_template +0 -0
  199. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/vu_logo_gen.py +0 -0
  200. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/vu_server.config_template +0 -0
  201. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/vu_server_config.py +0 -0
  202. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/vu_server_manager.py +0 -0
  203. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/vu_tui.py +0 -0
  204. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/vu/vu_updater.py +0 -0
  205. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/wigidash/README.md +0 -0
  206. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/wigidash/__init__.py +0 -0
  207. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/wigidash/assets/Inter-Bold.ttf +0 -0
  208. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/wigidash/assets/Inter.ttf +0 -0
  209. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/wigidash/assets/benchlab.png +0 -0
  210. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/wigidash/benchlab_fleet.py +0 -0
  211. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/wigidash/benchlab_graph.py +0 -0
  212. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/wigidash/benchlab_overview.py +0 -0
  213. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/wigidash/benchlab_telemetry.py +0 -0
  214. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/wigidash/benchlab_ui.py +0 -0
  215. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/wigidash/benchlab_utils.py +0 -0
  216. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/wigidash/requirements.txt +0 -0
  217. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/wigidash/test_wigidash.py +0 -0
  218. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/wigidash/wigidash_device.py +0 -0
  219. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/wigidash/wigidash_manager.py +0 -0
  220. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/wigidash/wigidash_session.py +0 -0
  221. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/wigidash/wigidash_usb.py +0 -0
  222. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab/wigidash/wigidash_widget.py +0 -0
  223. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/benchlab.py +0 -0
  224. {benchlab_pytools-3.3.0 → benchlab_pytools-3.4.0}/pytest.ini +0 -0
@@ -4,6 +4,40 @@ 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
+
24
+ ## [3.3.1] - 2026-10-17
25
+
26
+ ### Fixed
27
+ - Packaging: `benchlab-pytools[flash]` was missing `pydantic` and
28
+ `requests` (`flash_manager` imports `config_manager` -> `config.schema`
29
+ -> `pydantic`; `--source service_http` needs `requests`), so a clean
30
+ `[flash]`-only install crashed with `ModuleNotFoundError` on first use.
31
+ The launcher also reported `ModuleNotFoundError` as "Flash tool module
32
+ not available in this build" regardless of cause; it now prints the
33
+ real missing module and the install command. (#90)
34
+ - `check_bootloader_supported()` only gated BENCHLAB1 firmware against
35
+ the CMD_BOOTLOADER version floor; a BENCHLAB2 unit on firmware below
36
+ `0x05` (which predates CMD_BOOTLOADER on BL2, per the firmware repo's
37
+ protocol contract freeze history) passed the check and then hit a 5s
38
+ timeout in `enter_dfu_mode()` instead of being told up front to use a
39
+ physical BOOT0/jumper bootloader entry. (#94)
40
+
7
41
  ## [3.3.0] - 2026-10-01
8
42
 
9
43
  ### Added
@@ -193,6 +227,7 @@ export, MQTT publisher, VU dials, WigiDash, and config import/export tools,
193
227
  sharing a common data-source layer (direct serial, FastAPI, MQTT, named
194
228
  pipe, service HTTP, service WebSocket).
195
229
 
230
+ [3.3.1]: https://github.com/BenchLab-io/benchlab-pytools/compare/v3.3.0...v3.3.1
196
231
  [3.3.0]: https://github.com/BenchLab-io/benchlab-pytools/compare/v3.2.1...v3.3.0
197
232
  [3.2.1]: https://github.com/BenchLab-io/benchlab-pytools/compare/v3.2.0...v3.2.1
198
233
  [3.2.0]: https://github.com/BenchLab-io/benchlab-pytools/compare/v3.1.0...v3.2.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: benchlab-pytools
3
- Version: 3.3.0
3
+ Version: 3.4.0
4
4
  Summary: Python control suite for BENCHLAB telemetry devices
5
5
  Project-URL: Homepage, https://benchlab.io/
6
6
  Project-URL: Repository, https://github.com/BenchLab-io/benchlab-pytools
@@ -24,13 +24,14 @@ Classifier: Topic :: System :: Monitoring
24
24
  Requires-Python: >=3.10
25
25
  Requires-Dist: benchlab-pycore>=0.8.0
26
26
  Requires-Dist: packaging<26.0,>=23.0
27
+ Requires-Dist: pydantic>=2.0.0
27
28
  Requires-Dist: python-dotenv<2.0,>=1.2.2
28
29
  Requires-Dist: pywin32>=306; platform_system == 'Windows'
30
+ Requires-Dist: requests>=2.33.1
29
31
  Requires-Dist: textual<9.0,>=0.60
30
32
  Requires-Dist: windows-curses>=2.4.1; platform_system == 'Windows'
31
33
  Provides-Extra: all
32
34
  Requires-Dist: amqtt<0.13.0,>=0.12.0; extra == 'all'
33
- Requires-Dist: benchlab-pycore>=0.8.0; extra == 'all'
34
35
  Requires-Dist: blessed>=1.20.0; extra == 'all'
35
36
  Requires-Dist: dearpygui>=2.1.1; extra == 'all'
36
37
  Requires-Dist: fastapi==0.111.1; extra == 'all'
@@ -42,25 +43,20 @@ Requires-Dist: numpy>=2.2.0; extra == 'all'
42
43
  Requires-Dist: paho-mqtt<3.0,>=2.0; extra == 'all'
43
44
  Requires-Dist: pillow>=10.0.0; extra == 'all'
44
45
  Requires-Dist: pillow>=11.3.0; extra == 'all'
45
- Requires-Dist: pydantic>=2.0.0; extra == 'all'
46
46
  Requires-Dist: pyelftools>=0.31; extra == 'all'
47
47
  Requires-Dist: pyserial>=3.5; extra == 'all'
48
48
  Requires-Dist: python-dotenv==1.2.2; extra == 'all'
49
49
  Requires-Dist: pyusb>=1.3.1; extra == 'all'
50
50
  Requires-Dist: pyyaml<7.0,>=6.0; extra == 'all'
51
51
  Requires-Dist: pyyaml>=6.0; extra == 'all'
52
- Requires-Dist: requests>=2.31.0; extra == 'all'
53
- Requires-Dist: requests>=2.33.1; extra == 'all'
54
52
  Requires-Dist: ruamel-yaml; extra == 'all'
55
53
  Requires-Dist: tornado; extra == 'all'
56
54
  Requires-Dist: tqdm>=4.66; extra == 'all'
57
55
  Requires-Dist: uvicorn[standard]==0.24.0; extra == 'all'
58
56
  Requires-Dist: websockets<16.0,>=13.0; extra == 'all'
59
57
  Provides-Extra: csv-log
60
- Requires-Dist: benchlab-pycore>=0.8.0; extra == 'csv-log'
61
58
  Requires-Dist: pyserial>=3.5; extra == 'csv-log'
62
59
  Provides-Extra: flash
63
- Requires-Dist: benchlab-pycore>=0.8.0; extra == 'flash'
64
60
  Requires-Dist: intelhex>=2.3.0; extra == 'flash'
65
61
  Requires-Dist: libusb-package>=1.0.26.1; (sys_platform == 'win32') and extra == 'flash'
66
62
  Requires-Dist: pyelftools>=0.31; extra == 'flash'
@@ -72,29 +68,28 @@ Requires-Dist: dearpygui>=2.1.1; extra == 'graph'
72
68
  Requires-Dist: pyserial>=3.5; extra == 'graph'
73
69
  Provides-Extra: hwinfo
74
70
  Requires-Dist: pyserial>=3.5; extra == 'hwinfo'
71
+ Provides-Extra: link
72
+ Requires-Dist: paho-mqtt<3.0,>=2.0; extra == 'link'
73
+ Requires-Dist: pyserial>=3.5; extra == 'link'
75
74
  Provides-Extra: mqtt
76
75
  Requires-Dist: amqtt<0.13.0,>=0.12.0; extra == 'mqtt'
77
76
  Requires-Dist: paho-mqtt<3.0,>=2.0; extra == 'mqtt'
78
77
  Requires-Dist: pyyaml<7.0,>=6.0; extra == 'mqtt'
79
78
  Provides-Extra: restapi
80
79
  Requires-Dist: fastapi==0.111.1; extra == 'restapi'
81
- Requires-Dist: pydantic>=2.0.0; extra == 'restapi'
82
80
  Requires-Dist: pyserial>=3.5; extra == 'restapi'
83
81
  Requires-Dist: python-dotenv==1.2.2; extra == 'restapi'
84
82
  Requires-Dist: uvicorn[standard]==0.24.0; extra == 'restapi'
85
83
  Provides-Extra: service-ws
86
84
  Requires-Dist: websockets<16.0,>=13.0; extra == 'service-ws'
87
85
  Provides-Extra: tui
88
- Requires-Dist: pydantic>=2.0.0; extra == 'tui'
89
86
  Requires-Dist: pyserial>=3.5; extra == 'tui'
90
- Requires-Dist: requests>=2.33.1; extra == 'tui'
91
87
  Provides-Extra: vu
92
88
  Requires-Dist: blessed>=1.20.0; extra == 'vu'
93
89
  Requires-Dist: numpy; extra == 'vu'
94
90
  Requires-Dist: pillow>=10.0.0; extra == 'vu'
95
91
  Requires-Dist: pyserial>=3.5; extra == 'vu'
96
92
  Requires-Dist: pyyaml>=6.0; extra == 'vu'
97
- Requires-Dist: requests>=2.31.0; extra == 'vu'
98
93
  Requires-Dist: ruamel-yaml; extra == 'vu'
99
94
  Requires-Dist: tornado; extra == 'vu'
100
95
  Provides-Extra: wigidash
@@ -191,6 +186,22 @@ python -m benchlab -config ... # Device configuration import/export
191
186
  python -m benchlab -flash ... # Firmware flash tool (STM32 ROM DFU)
192
187
  ```
193
188
 
189
+ The flash tool needs its extra: `pip install "benchlab-pytools[flash]"`. It takes a firmware
190
+ image with `--file` and does not download one; releases live in
191
+ [BENCHLAB-FW](https://github.com/BenchLab-io/BENCHLAB-FW/releases). Start with `--list` to see what
192
+ is connected, and `--verify-only` to check an image against a device without erasing or writing:
193
+
194
+ ```bash
195
+ python -m benchlab -flash --list
196
+ python -m benchlab -flash --file benchlab2-fw07-v0.7.2.bin --verify-only
197
+ ```
198
+
199
+ **On Windows, flashing needs a one-time driver step.** In DFU mode the board enumerates as
200
+ ST's ROM bootloader (VID `0483`, PID `DF11`), which does not bind to a driver libusb can use,
201
+ so the tool cannot see a device that is plugged in and sitting in its bootloader. Bind WinUSB
202
+ to it with [Zadig](https://zadig.akeo.ie/) first -- see
203
+ [Windows: USB driver setup](benchlab/flash/README.md#windows-usb-driver-setup-zadig).
204
+
194
205
  Running with no flags is equivalent to launching the interactive menu.
195
206
 
196
207
  ### Data Sources
@@ -83,6 +83,22 @@ python -m benchlab -config ... # Device configuration import/export
83
83
  python -m benchlab -flash ... # Firmware flash tool (STM32 ROM DFU)
84
84
  ```
85
85
 
86
+ The flash tool needs its extra: `pip install "benchlab-pytools[flash]"`. It takes a firmware
87
+ image with `--file` and does not download one; releases live in
88
+ [BENCHLAB-FW](https://github.com/BenchLab-io/BENCHLAB-FW/releases). Start with `--list` to see what
89
+ is connected, and `--verify-only` to check an image against a device without erasing or writing:
90
+
91
+ ```bash
92
+ python -m benchlab -flash --list
93
+ python -m benchlab -flash --file benchlab2-fw07-v0.7.2.bin --verify-only
94
+ ```
95
+
96
+ **On Windows, flashing needs a one-time driver step.** In DFU mode the board enumerates as
97
+ ST's ROM bootloader (VID `0483`, PID `DF11`), which does not bind to a driver libusb can use,
98
+ so the tool cannot see a device that is plugged in and sitting in its bootloader. Bind WinUSB
99
+ to it with [Zadig](https://zadig.akeo.ie/) first -- see
100
+ [Windows: USB driver setup](benchlab/flash/README.md#windows-usb-driver-setup-zadig).
101
+
86
102
  Running with no flags is equivalent to launching the interactive menu.
87
103
 
88
104
  ### Data Sources
@@ -2,4 +2,4 @@
2
2
  BENCHLAB Telemetry Package
3
3
  """
4
4
 
5
- __version__ = "3.3.0"
5
+ __version__ = "3.4.0"
@@ -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
 
@@ -178,10 +178,26 @@ WinUSB (or libusbK) driver to it using
178
178
 
179
179
  1. Start the flash operation so the device enters DFU mode (or put it into
180
180
  DFU mode and let it stay there briefly).
181
- 2. Open Zadig, enable "List All Devices", find the device (it should show
182
- up as an STM32 BOOTLOADER or similar, VID/PID `0483`/`DF11`).
183
- 3. Select the WinUSB driver and click "Install Driver" / "Replace Driver".
184
- 4. Re-run the flash command.
181
+ 2. Open Zadig **as Administrator** and find the device -- it should show up
182
+ as `STM32 BOOTLOADER`. A driverless device is listed by default; only
183
+ enable "List All Devices" if it does not appear.
184
+ 3. **Check the USB ID reads `0483 DF11`** before continuing, so the driver
185
+ cannot go onto the wrong device.
186
+ 4. Select the WinUSB driver and click "Install Driver" / "Replace Driver".
187
+ This takes around 30 seconds and the window may appear frozen.
188
+ 5. Re-run the flash command.
189
+
190
+ To confirm the bind before re-running, on Windows:
191
+
192
+ ```powershell
193
+ Get-PnpDevice | Where-Object { $_.InstanceId -like '*VID_0483&PID_DF11*' } |
194
+ Select-Object Status, FriendlyName, Service
195
+ ```
196
+
197
+ `Service : WinUSB` with `Status : OK` means libusb can now see it. An
198
+ unbound device instead reports `Status : Error` and
199
+ `ConfigManagerErrorCode : 28` ("no driver installed"), which is the
200
+ underlying cause of the timeout above.
185
201
 
186
202
  This is a one-time setup per machine/USB port combination. This is a
187
203
  Windows driver-binding limitation, independent of this tool being pure
@@ -32,6 +32,11 @@ logger = logging.getLogger("benchlab.flash.manager")
32
32
  # ignore the opcode).
33
33
  MIN_BOOTLOADER_FW_VERSION = 0x04
34
34
 
35
+ # Same opcode, same commit (c722244), but BL2's frozen protocol contracts
36
+ # show it landed between the fw04 and fw05 freezes -- benchlab2-fw03.json/
37
+ # benchlab2-fw04.json have no BOOTLOADER entry, benchlab2-fw05.json does.
38
+ MIN_BOOTLOADER_FW_VERSION_BL2 = 0x05
39
+
35
40
  _KNOWN_PRODUCT_IDS = (BENCHLAB_ORIGINAL_PRODUCT_ID, BENCHLAB_BL2_PRODUCT_ID)
36
41
  _BENCHLAB_VENDOR_ID = 0xEE
37
42
 
@@ -155,16 +160,20 @@ class FlashManager:
155
160
 
156
161
  def check_bootloader_supported(
157
162
  self, device_info: dict, product_id: int) -> None:
158
- if (product_id == BENCHLAB_ORIGINAL_PRODUCT_ID
159
- and device_info.get("FwVersion", 0)
160
- < MIN_BOOTLOADER_FW_VERSION):
163
+ if product_id == BENCHLAB_ORIGINAL_PRODUCT_ID:
164
+ min_fw = MIN_BOOTLOADER_FW_VERSION
165
+ elif product_id == BENCHLAB_BL2_PRODUCT_ID:
166
+ min_fw = MIN_BOOTLOADER_FW_VERSION_BL2
167
+ else:
168
+ return
169
+
170
+ if device_info.get("FwVersion", 0) < min_fw:
161
171
  raise UnsupportedFirmwareError(
162
172
  "This device's firmware "
163
173
  f"(0x{device_info.get('FwVersion', 0):02X}) predates "
164
- f"CMD_BOOTLOADER support (requires "
165
- f"0x{MIN_BOOTLOADER_FW_VERSION:02X}+). It cannot be put "
166
- "into DFU mode via software -- use a physical BOOT0/jumper "
167
- "bootloader entry instead.")
174
+ f"CMD_BOOTLOADER support (requires 0x{min_fw:02X}+). It "
175
+ "cannot be put into DFU mode via software -- use a "
176
+ "physical BOOT0/jumper bootloader entry instead.")
168
177
 
169
178
  def enter_dfu_mode(
170
179
  self, identifier: str, port: Optional[str] = None,
@@ -316,7 +325,9 @@ class FlashManager:
316
325
 
317
326
  logger.warning(
318
327
  "%s did not reappear within %.0fs after leaving DFU mode -- "
319
- "this may just be slow re-enumeration, verify manually.",
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.",
320
331
  port, timeout)
321
332
  return None
322
333
 
@@ -344,7 +355,11 @@ class FlashManager:
344
355
  "Flash completed but verification failed")
345
356
 
346
357
  if port is not None:
347
- 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")
348
363
  else:
349
364
  # No CDC port to wait for -- just trigger the DfuSe leave
350
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
 
@@ -0,0 +1,11 @@
1
+ pyserial>=3.5
2
+ pyusb>=1.3.1
3
+ intelhex>=2.3.0
4
+ pyelftools>=0.31
5
+ tqdm>=4.66
6
+ benchlab-pycore>=0.8.0
7
+ libusb-package>=1.0.26.1; sys_platform == 'win32'
8
+ # flash_manager -> config_manager -> config.schema (see #90)
9
+ pydantic>=2.0.0
10
+ # needed by --source service_http (see #90)
11
+ requests>=2.33.1
@@ -108,11 +108,22 @@ def test_check_bootloader_supported_bl1_version_gate(
108
108
  info, BENCHLAB_ORIGINAL_PRODUCT_ID)
109
109
 
110
110
 
111
- def test_check_bootloader_supported_bl2_not_version_gated(manager):
112
- # BL2's CMD_BOOTLOADER gating isn't modeled here (all shipped BL2
113
- # firmware postdates it) -- should never raise regardless of fw value.
114
- info = _device_info(product_id=BENCHLAB_BL2_PRODUCT_ID, fw=0x01)
115
- manager.check_bootloader_supported(info, BENCHLAB_BL2_PRODUCT_ID)
111
+ @pytest.mark.parametrize("fw,should_raise", [
112
+ (0x03, True),
113
+ (0x04, True),
114
+ (0x05, False),
115
+ (0x07, False),
116
+ ])
117
+ def test_check_bootloader_supported_bl2_version_gate(
118
+ manager, fw, should_raise):
119
+ info = _device_info(product_id=BENCHLAB_BL2_PRODUCT_ID, fw=fw)
120
+ if should_raise:
121
+ with pytest.raises(UnsupportedFirmwareError):
122
+ manager.check_bootloader_supported(
123
+ info, BENCHLAB_BL2_PRODUCT_ID)
124
+ else:
125
+ manager.check_bootloader_supported(
126
+ info, BENCHLAB_BL2_PRODUCT_ID)
116
127
 
117
128
 
118
129
  # -- enter_dfu_mode / wait_for_dfu_device ----------------------------------
@@ -354,6 +365,33 @@ def test_flash_bare_dfu_flashes_without_cdc_round_trip(
354
365
  assert "Flashed and verified successfully" in result.message
355
366
 
356
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
+
357
395
  def test_flash_bare_dfu_catches_exceptions(manager, monkeypatch):
358
396
  monkeypatch.setattr(
359
397
  manager, "wait_for_dfu_device",
@@ -0,0 +1,3 @@
1
+ # benchlab/link/requirements.txt
2
+ paho-mqtt>=2.0,<3.0
3
+ pyserial>=3.5
@@ -402,34 +402,50 @@ def launch_mode() -> None:
402
402
  try:
403
403
  sys.argv.remove('-config')
404
404
  from benchlab.config.config_tool import main as config_main
405
+ except ModuleNotFoundError as exc:
406
+ # A missing transitive dependency is not a missing build: name the
407
+ # real cause. There is no install hint because config's only
408
+ # third-party import (pydantic) is a core dependency, so a miss
409
+ # here means a broken install rather than a missing extra (#90,
410
+ # #92).
411
+ print(f"Config tool unavailable: {exc}")
412
+ else:
405
413
  sys.exit(config_main())
406
- except ModuleNotFoundError:
407
- print("Config tool module not available in this build.")
408
414
 
409
415
  elif args.flash:
410
416
  # Flash tool handles its own argument parsing
411
417
  try:
412
418
  sys.argv.remove('-flash')
413
419
  from benchlab.flash.flash_tool import main as flash_main
420
+ except ModuleNotFoundError as exc:
421
+ # A missing transitive dependency is not a missing build: name
422
+ # it, and point at the extra that supplies it (see #90).
423
+ print(f"Flash tool unavailable: {exc}")
424
+ print('Install it with: pip install "benchlab-pytools[flash]"')
425
+ else:
414
426
  sys.exit(flash_main())
415
- except ModuleNotFoundError:
416
- print("Flash tool module not available in this build.")
417
427
 
418
428
  elif args.fastapi:
419
429
  try:
420
430
  from benchlab.restapi.telemetry_api import run_server
431
+ except ModuleNotFoundError as exc:
432
+ # Same reasoning as -flash: a missing dependency is not a missing
433
+ # build, so report which module is absent (see #90).
434
+ print(f"FastAPI / Uvicorn unavailable: {exc}")
435
+ print('Install it with: pip install "benchlab-pytools[restapi]"')
436
+ else:
421
437
  run_server()
422
- except ModuleNotFoundError:
423
- print("FastAPI / Uvicorn not available in this build.")
424
438
 
425
439
  elif args.mqtt:
426
440
  try:
427
441
  from benchlab.mqtt.mqtt_publisher import run_mqtt_mode
442
+ except ModuleNotFoundError as exc:
443
+ print(f"MQTT module unavailable: {exc}")
444
+ print('Install it with: pip install "benchlab-pytools[mqtt]"')
445
+ else:
428
446
  broker = args.mqtt if args.mqtt else "localhost"
429
447
  os.environ.setdefault("MQTT_BROKER", broker)
430
448
  run_mqtt_mode(broker)
431
- except ModuleNotFoundError:
432
- print("MQTT module not available in this build.")
433
449
 
434
450
  elif args.link:
435
451
  _export_link_env(args)
@@ -0,0 +1,3 @@
1
+ paho-mqtt>=2.0,<3.0
2
+ amqtt>=0.12.0,<0.13.0
3
+ pyyaml>=6.0,<7.0
@@ -25,7 +25,9 @@ CONSUMER_TOOLS = {
25
25
  "flag": "-config",
26
26
  "module": "benchlab.config.config_tool",
27
27
  "function": "main",
28
- "requirements": "requirements.txt",
28
+ # No requirements file: config's only third-party import is pydantic,
29
+ # which is a core dependency, so there is nothing tool-specific to
30
+ # install (see #92).
29
31
  "supported_sources": ["direct", "named_pipe"],
30
32
  },
31
33
  "flash": {
@@ -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 8 tabs: `Fleet`, `Device`, `System`, `Motherboard`, `12VHPWR`, `Voltage`, `Temperature`, `Fans` (in this order; see `Config.TAB_NAMES` in `benchlab/tui/config.py`). Tabs can also be jumped to directly with the number keys `0`-`7`.
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`-`7` - Jump directly to a tab by index
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