benchlab-pytools 3.2.1__tar.gz → 3.3.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 (221) hide show
  1. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/CHANGELOG.md +21 -0
  2. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/PKG-INFO +16 -2
  3. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/README.md +4 -1
  4. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/__init__.py +1 -1
  5. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/config/README.md +49 -6
  6. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/config/config_client.py +238 -3
  7. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/config/config_manager.py +76 -6
  8. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/config/config_tool.py +34 -7
  9. benchlab_pytools-3.3.0/benchlab/config/http_dto.py +109 -0
  10. benchlab_pytools-3.3.0/benchlab/config/test_http_config_client.py +153 -0
  11. benchlab_pytools-3.3.0/benchlab/config/test_http_dto.py +115 -0
  12. benchlab_pytools-3.3.0/benchlab/flash/README.md +213 -0
  13. benchlab_pytools-3.3.0/benchlab/flash/__init__.py +6 -0
  14. benchlab_pytools-3.3.0/benchlab/flash/dfu.py +249 -0
  15. benchlab_pytools-3.3.0/benchlab/flash/flash_manager.py +500 -0
  16. benchlab_pytools-3.3.0/benchlab/flash/flash_tool.py +624 -0
  17. benchlab_pytools-3.3.0/benchlab/flash/image.py +178 -0
  18. benchlab_pytools-3.3.0/benchlab/flash/requirements.txt +7 -0
  19. benchlab_pytools-3.3.0/benchlab/flash/test_dfu.py +211 -0
  20. benchlab_pytools-3.3.0/benchlab/flash/test_flash_manager.py +549 -0
  21. benchlab_pytools-3.3.0/benchlab/flash/test_flash_tool.py +350 -0
  22. benchlab_pytools-3.3.0/benchlab/flash/test_image.py +158 -0
  23. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/main.py +13 -1
  24. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/tools.py +9 -0
  25. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/pyproject.toml +10 -1
  26. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/.flake8 +0 -0
  27. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/.gitignore +0 -0
  28. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/CONTRIBUTING.md +0 -0
  29. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/LICENSE +0 -0
  30. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/__main__.py +0 -0
  31. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/bootstrap.py +0 -0
  32. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/config/__init__.py +0 -0
  33. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/config/diff.py +0 -0
  34. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/config/examples/complete_config.json +0 -0
  35. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/config/examples/rgb_lighting.json +0 -0
  36. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/config/examples/simple_fan.json +0 -0
  37. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/config/schema.py +0 -0
  38. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/config/test_config_client.py +0 -0
  39. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/config/test_config_manager.py +0 -0
  40. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/config/test_diff.py +0 -0
  41. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/core/README.md +0 -0
  42. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/core/__init__.py +0 -0
  43. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/core/config.py +0 -0
  44. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/core/datasource.py +0 -0
  45. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/core/datasource_manager.py +0 -0
  46. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/core/device_registry.py +0 -0
  47. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/core/discovery.py +0 -0
  48. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/core/infrastructure.py +0 -0
  49. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/core/process_manager.py +0 -0
  50. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/core/retry.py +0 -0
  51. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/core/shared_serial.py +0 -0
  52. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/core/statistics.py +0 -0
  53. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/csv_log/README.md +0 -0
  54. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/csv_log/__init__.py +0 -0
  55. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/csv_log/csv_logger_enhanced.py +0 -0
  56. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/csv_log/message_batcher.py +0 -0
  57. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/csv_log/requirements.txt +0 -0
  58. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/csv_log/smart_retry.py +0 -0
  59. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/graph/README.md +0 -0
  60. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/graph/__init__.py +0 -0
  61. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/graph/app.py +0 -0
  62. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/graph/requirements.txt +0 -0
  63. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/graph/runner.py +0 -0
  64. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/graph/test_graph.py +0 -0
  65. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/graph/ui.py +0 -0
  66. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/hwinfo/README.md +0 -0
  67. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/hwinfo/__init__.py +0 -0
  68. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/hwinfo/hwinfo_export.py +0 -0
  69. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/hwinfo/requirements.txt +0 -0
  70. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/hwinfo/test_hwinfo_export.py +0 -0
  71. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/launcher.py +0 -0
  72. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/link/.env.EXAMPLE +0 -0
  73. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/link/README.md +0 -0
  74. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/link/__init__.py +0 -0
  75. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/link/link_main.py +0 -0
  76. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/link/test_link_main.py +0 -0
  77. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/menu.py +0 -0
  78. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/menu_classic.py +0 -0
  79. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/mqtt/README.md +0 -0
  80. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/mqtt/__init__.py +0 -0
  81. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/mqtt/mqtt.config_template +0 -0
  82. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/mqtt/mqtt_publisher.py +0 -0
  83. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/mqtt/requirements.txt +0 -0
  84. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/mqtt/test_mqtt_publisher.py +0 -0
  85. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/prefs.py +0 -0
  86. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/requirements.txt +0 -0
  87. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/restapi/.env.example +0 -0
  88. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/restapi/__init__.py +0 -0
  89. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/restapi/favicon.ico +0 -0
  90. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/restapi/readme.md +0 -0
  91. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/restapi/requirements.txt +0 -0
  92. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/restapi/telemetry_api.py +0 -0
  93. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/restapi/test_read_recovery.py +0 -0
  94. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/restapi/test_server.py +0 -0
  95. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/sources.py +0 -0
  96. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/tui/README.md +0 -0
  97. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/tui/__init__.py +0 -0
  98. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/tui/config.py +0 -0
  99. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/tui/requirements.txt +0 -0
  100. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/tui/test_tui_core.py +0 -0
  101. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/tui/test_tui_main.py +0 -0
  102. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/tui/tui_core.py +0 -0
  103. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/tui/tui_main.py +0 -0
  104. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/README.md +0 -0
  105. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/NOTICE.md +0 -0
  106. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/README.md +0 -0
  107. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/Running_from_source_on_linux.md +0 -0
  108. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/__init__.py +0 -0
  109. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/assets/vu1_hello_world.png +0 -0
  110. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/community_applications.md +0 -0
  111. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/config.yaml +0 -0
  112. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/database.py +0 -0
  113. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/dial_driver.py +0 -0
  114. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/dials/Comms_Hub_Gauge.py +0 -0
  115. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/dials/Comms_Hub_Server.py +0 -0
  116. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/dials/__init__.py +0 -0
  117. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/dials/base_logger.py +0 -0
  118. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/installer/inc/icon.ico +0 -0
  119. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/installer/install.nsi +0 -0
  120. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/make_version.py +0 -0
  121. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/pylintrc +0 -0
  122. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/requirements.txt +0 -0
  123. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/serial_driver.py +0 -0
  124. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/server.py +0 -0
  125. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/server_config.py +0 -0
  126. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/server_dial_handler.py +0 -0
  127. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/vu_notifications.py +0 -0
  128. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/android-chrome-192x192.png +0 -0
  129. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/android-chrome-512x512.png +0 -0
  130. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/apple-touch-icon.png +0 -0
  131. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/demo.css +0 -0
  132. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/demo.min.css +0 -0
  133. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/demo.rtl.css +0 -0
  134. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/demo.rtl.min.css +0 -0
  135. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.css +0 -0
  136. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.min.css +0 -0
  137. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.css +0 -0
  138. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.min.css +0 -0
  139. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.css +0 -0
  140. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.min.css +0 -0
  141. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.css +0 -0
  142. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.min.css +0 -0
  143. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.css +0 -0
  144. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.min.css +0 -0
  145. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.css +0 -0
  146. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.min.css +0 -0
  147. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.css +0 -0
  148. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.min.css +0 -0
  149. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.css +0 -0
  150. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.min.css +0 -0
  151. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler.css +0 -0
  152. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler.min.css +0 -0
  153. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler.rtl.css +0 -0
  154. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/tabler.rtl.min.css +0 -0
  155. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/css/theme.min.css +0 -0
  156. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/img/header.png +0 -0
  157. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/img/logo.png +0 -0
  158. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/js/dark-theme.js +0 -0
  159. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/js/dark-theme.min.js +0 -0
  160. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/js/demo.js +0 -0
  161. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/js/demo.min.js +0 -0
  162. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/js/jquery.min.js +0 -0
  163. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/js/tabler.esm.js +0 -0
  164. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/js/tabler.esm.min.js +0 -0
  165. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/js/tabler.js +0 -0
  166. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/js/tabler.min.js +0 -0
  167. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/js/vu1_api.js +0 -0
  168. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_key_settings.js +0 -0
  169. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_keys.js +0 -0
  170. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_dial.js +0 -0
  171. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_home.js +0 -0
  172. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/assets/js/vu1_gui_root.js +0 -0
  173. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/favicon-16x16.png +0 -0
  174. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/favicon-32x32.png +0 -0
  175. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/favicon.ico +0 -0
  176. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/favicon.png +0 -0
  177. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/index.html +0 -0
  178. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/views/api_keys.html +0 -0
  179. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/views/dial.html +0 -0
  180. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/views/footer.html +0 -0
  181. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/views/header.html +0 -0
  182. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/views/key_settings.html +0 -0
  183. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/VU-Server/www/views/start.html +0 -0
  184. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/__init__.py +0 -0
  185. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/assets/Barlow-Bold.ttf +0 -0
  186. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/assets/bl_dial_144x200.png +0 -0
  187. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/assets/bl_logo_144x200.png +0 -0
  188. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/devices.py +0 -0
  189. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/requirements.txt +0 -0
  190. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/sensors.py +0 -0
  191. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/test_devices.py +0 -0
  192. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/test_vu_server_config.py +0 -0
  193. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/test_vu_server_manager.py +0 -0
  194. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/test_vu_tui.py +0 -0
  195. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/vu_dial.config_template +0 -0
  196. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/vu_logo_gen.py +0 -0
  197. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/vu_server.config_template +0 -0
  198. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/vu_server_config.py +0 -0
  199. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/vu_server_manager.py +0 -0
  200. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/vu_tui.py +0 -0
  201. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/vu/vu_updater.py +0 -0
  202. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/wigidash/README.md +0 -0
  203. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/wigidash/__init__.py +0 -0
  204. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/wigidash/assets/Inter-Bold.ttf +0 -0
  205. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/wigidash/assets/Inter.ttf +0 -0
  206. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/wigidash/assets/benchlab.png +0 -0
  207. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/wigidash/benchlab_fleet.py +0 -0
  208. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/wigidash/benchlab_graph.py +0 -0
  209. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/wigidash/benchlab_overview.py +0 -0
  210. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/wigidash/benchlab_telemetry.py +0 -0
  211. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/wigidash/benchlab_ui.py +0 -0
  212. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/wigidash/benchlab_utils.py +0 -0
  213. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/wigidash/requirements.txt +0 -0
  214. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/wigidash/test_wigidash.py +0 -0
  215. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/wigidash/wigidash_device.py +0 -0
  216. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/wigidash/wigidash_manager.py +0 -0
  217. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/wigidash/wigidash_session.py +0 -0
  218. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/wigidash/wigidash_usb.py +0 -0
  219. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab/wigidash/wigidash_widget.py +0 -0
  220. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/benchlab.py +0 -0
  221. {benchlab_pytools-3.2.1 → benchlab_pytools-3.3.0}/pytest.ini +0 -0
@@ -4,6 +4,26 @@ 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.3.0] - 2026-10-01
8
+
9
+ ### Added
10
+ - Firmware flash tool (`-flash`, `benchlab.flash`): flashes new firmware
11
+ onto BENCHLAB1/2 devices (STM32F303RB) over USB DFU, commanding the
12
+ running firmware to jump into ST's factory ROM bootloader and talking to
13
+ it via a pure-Python `pyusb` client (no external flashing binary
14
+ required). Supports `.bin` (primary), `.hex`, and `.elf` images, and
15
+ `direct`/`named_pipe`/`service_http` sources for the initial CDC-side
16
+ bootloader jump. Falls back to a guided manual BOOT0 entry for firmware
17
+ too old to support the CDC bootloader command. (#84)
18
+ - `HttpConfigClient` (`service_http` source) now implements fan/RGB/
19
+ calibration read-write (`read_fan_config`/`write_fan_config`,
20
+ `read_rgb_config`/`write_rgb_config`, `read_calibration`/
21
+ `write_calibration`), via a new translation layer
22
+ (`benchlab.config.http_dto`) between the BL_Service HTTP API's DTO JSON
23
+ shapes and this repo's existing config-dict convention. Handles BL2's
24
+ wider calibration wire struct correctly. `config_tool.py` (`-config`)
25
+ gained `--source service_http` CLI support to match. (#86)
26
+
7
27
  ## [3.2.1] - 2026-10-01
8
28
 
9
29
  ### Fixed
@@ -173,6 +193,7 @@ export, MQTT publisher, VU dials, WigiDash, and config import/export tools,
173
193
  sharing a common data-source layer (direct serial, FastAPI, MQTT, named
174
194
  pipe, service HTTP, service WebSocket).
175
195
 
196
+ [3.3.0]: https://github.com/BenchLab-io/benchlab-pytools/compare/v3.2.1...v3.3.0
176
197
  [3.2.1]: https://github.com/BenchLab-io/benchlab-pytools/compare/v3.2.0...v3.2.1
177
198
  [3.2.0]: https://github.com/BenchLab-io/benchlab-pytools/compare/v3.1.0...v3.2.0
178
199
  [3.1.0]: https://github.com/BenchLab-io/benchlab-pytools/compare/v3.0.4...v3.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: benchlab-pytools
3
- Version: 3.2.1
3
+ Version: 3.3.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
@@ -34,6 +34,7 @@ Requires-Dist: benchlab-pycore>=0.8.0; extra == 'all'
34
34
  Requires-Dist: blessed>=1.20.0; extra == 'all'
35
35
  Requires-Dist: dearpygui>=2.1.1; extra == 'all'
36
36
  Requires-Dist: fastapi==0.111.1; extra == 'all'
37
+ Requires-Dist: intelhex>=2.3.0; extra == 'all'
37
38
  Requires-Dist: libusb-package>=1.0.26.1; (sys_platform == 'win32') and extra == 'all'
38
39
  Requires-Dist: matplotlib>=3.10.7; extra == 'all'
39
40
  Requires-Dist: numpy; extra == 'all'
@@ -42,6 +43,7 @@ Requires-Dist: paho-mqtt<3.0,>=2.0; extra == 'all'
42
43
  Requires-Dist: pillow>=10.0.0; extra == 'all'
43
44
  Requires-Dist: pillow>=11.3.0; extra == 'all'
44
45
  Requires-Dist: pydantic>=2.0.0; extra == 'all'
46
+ Requires-Dist: pyelftools>=0.31; extra == 'all'
45
47
  Requires-Dist: pyserial>=3.5; extra == 'all'
46
48
  Requires-Dist: python-dotenv==1.2.2; extra == 'all'
47
49
  Requires-Dist: pyusb>=1.3.1; extra == 'all'
@@ -51,11 +53,20 @@ Requires-Dist: requests>=2.31.0; extra == 'all'
51
53
  Requires-Dist: requests>=2.33.1; extra == 'all'
52
54
  Requires-Dist: ruamel-yaml; extra == 'all'
53
55
  Requires-Dist: tornado; extra == 'all'
56
+ Requires-Dist: tqdm>=4.66; extra == 'all'
54
57
  Requires-Dist: uvicorn[standard]==0.24.0; extra == 'all'
55
58
  Requires-Dist: websockets<16.0,>=13.0; extra == 'all'
56
59
  Provides-Extra: csv-log
57
60
  Requires-Dist: benchlab-pycore>=0.8.0; extra == 'csv-log'
58
61
  Requires-Dist: pyserial>=3.5; extra == 'csv-log'
62
+ Provides-Extra: flash
63
+ Requires-Dist: benchlab-pycore>=0.8.0; extra == 'flash'
64
+ Requires-Dist: intelhex>=2.3.0; extra == 'flash'
65
+ Requires-Dist: libusb-package>=1.0.26.1; (sys_platform == 'win32') and extra == 'flash'
66
+ Requires-Dist: pyelftools>=0.31; extra == 'flash'
67
+ Requires-Dist: pyserial>=3.5; extra == 'flash'
68
+ Requires-Dist: pyusb>=1.3.1; extra == 'flash'
69
+ Requires-Dist: tqdm>=4.66; extra == 'flash'
59
70
  Provides-Extra: graph
60
71
  Requires-Dist: dearpygui>=2.1.1; extra == 'graph'
61
72
  Requires-Dist: pyserial>=3.5; extra == 'graph'
@@ -109,6 +120,7 @@ BENCHLAB PyTools is the Python-based control suite for BENCHLAB telemetry device
109
120
  - **VU Dials** — analog-style VU meter dial display and configuration
110
121
  - **WigiDash** — telemetry/graph display on a G.SKILL WigiDash panel
111
122
  - **Config Tool** — import/export device configuration (fan curves, RGB, etc.) via JSON
123
+ - **Flash Tool** — flash new firmware onto BENCHLAB1/2 devices over USB DFU
112
124
 
113
125
  All tools share a common data-source layer, so the same telemetry can be read directly from a device, through a FastAPI server, over MQTT, or via the Windows BENCHLAB service — locally or remotely — without changing the consumer tool.
114
126
 
@@ -176,6 +188,7 @@ python -m benchlab -vu # VU analog dials
176
188
  python -m benchlab -vuconfig # VU dial configuration UI
177
189
  python -m benchlab -wigidash # WigiDash display
178
190
  python -m benchlab -config ... # Device configuration import/export
191
+ python -m benchlab -flash ... # Firmware flash tool (STM32 ROM DFU)
179
192
  ```
180
193
 
181
194
  Running with no flags is equivalent to launching the interactive menu.
@@ -218,7 +231,7 @@ Common connection flags:
218
231
  -i, --interval SECONDS Refresh interval (default: 1.0)
219
232
  ```
220
233
 
221
- When a source needs a background service (`fastapi`, `mqtt`), the launcher starts and health-checks it automatically, and tears it down on exit. Not every tool supports every source — the config tool, for example, only supports `direct` and `named_pipe`. See each tool's README for specifics.
234
+ When a source needs a background service (`fastapi`, `mqtt`), the launcher starts and health-checks it automatically, and tears it down on exit. Not every tool supports every source — the config and flash tools, for example, only support `direct`, `named_pipe`, and `service_http`. See each tool's README for specifics.
222
235
 
223
236
  ### Launch Profiles
224
237
 
@@ -259,6 +272,7 @@ Each consumer tool lives in its own subpackage under `benchlab/` with its own RE
259
272
  - [Core](benchlab/core/README.md) — shared internals (data sources, process management, discovery)
260
273
  - [CSV Logger](benchlab/csv_log/README.md)
261
274
  - [FastAPI Server](benchlab/restapi/readme.md)
275
+ - [Flash Tool](benchlab/flash/README.md) — firmware flashing over USB DFU
262
276
  - [Graph](benchlab/graph/README.md)
263
277
  - [HWiNFO Export](benchlab/hwinfo/README.md)
264
278
  - [Link](benchlab/link/README.md) — publish telemetry to BENCHLAB cloud
@@ -12,6 +12,7 @@ BENCHLAB PyTools is the Python-based control suite for BENCHLAB telemetry device
12
12
  - **VU Dials** — analog-style VU meter dial display and configuration
13
13
  - **WigiDash** — telemetry/graph display on a G.SKILL WigiDash panel
14
14
  - **Config Tool** — import/export device configuration (fan curves, RGB, etc.) via JSON
15
+ - **Flash Tool** — flash new firmware onto BENCHLAB1/2 devices over USB DFU
15
16
 
16
17
  All tools share a common data-source layer, so the same telemetry can be read directly from a device, through a FastAPI server, over MQTT, or via the Windows BENCHLAB service — locally or remotely — without changing the consumer tool.
17
18
 
@@ -79,6 +80,7 @@ python -m benchlab -vu # VU analog dials
79
80
  python -m benchlab -vuconfig # VU dial configuration UI
80
81
  python -m benchlab -wigidash # WigiDash display
81
82
  python -m benchlab -config ... # Device configuration import/export
83
+ python -m benchlab -flash ... # Firmware flash tool (STM32 ROM DFU)
82
84
  ```
83
85
 
84
86
  Running with no flags is equivalent to launching the interactive menu.
@@ -121,7 +123,7 @@ Common connection flags:
121
123
  -i, --interval SECONDS Refresh interval (default: 1.0)
122
124
  ```
123
125
 
124
- When a source needs a background service (`fastapi`, `mqtt`), the launcher starts and health-checks it automatically, and tears it down on exit. Not every tool supports every source — the config tool, for example, only supports `direct` and `named_pipe`. See each tool's README for specifics.
126
+ When a source needs a background service (`fastapi`, `mqtt`), the launcher starts and health-checks it automatically, and tears it down on exit. Not every tool supports every source — the config and flash tools, for example, only support `direct`, `named_pipe`, and `service_http`. See each tool's README for specifics.
125
127
 
126
128
  ### Launch Profiles
127
129
 
@@ -162,6 +164,7 @@ Each consumer tool lives in its own subpackage under `benchlab/` with its own RE
162
164
  - [Core](benchlab/core/README.md) — shared internals (data sources, process management, discovery)
163
165
  - [CSV Logger](benchlab/csv_log/README.md)
164
166
  - [FastAPI Server](benchlab/restapi/readme.md)
167
+ - [Flash Tool](benchlab/flash/README.md) — firmware flashing over USB DFU
165
168
  - [Graph](benchlab/graph/README.md)
166
169
  - [HWiNFO Export](benchlab/hwinfo/README.md)
167
170
  - [Link](benchlab/link/README.md) — publish telemetry to BENCHLAB cloud
@@ -2,4 +2,4 @@
2
2
  BENCHLAB Telemetry Package
3
3
  """
4
4
 
5
- __version__ = "3.2.1"
5
+ __version__ = "3.3.0"
@@ -1,12 +1,12 @@
1
1
  # BENCHLAB Device Configuration Tool
2
2
 
3
- A command-line tool for importing and exporting BENCHLAB device configuration via JSON files. Supports both direct serial (pycore) and Windows named pipe (BL_Service) data sources.
3
+ A command-line tool for importing and exporting BENCHLAB device configuration via JSON files. Supports direct serial (pycore), Windows named pipe (BL_Service), and the BL_Service HTTP API as data sources.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - **Export Configuration**: Read current device settings and save to JSON
8
8
  - **Import Configuration**: Apply JSON configuration to devices
9
- - **Multi-Source Support**: Works with both direct serial and named pipe connections
9
+ - **Multi-Source Support**: Works with direct serial, named pipe, and HTTP connections
10
10
  - **Interactive Mode**: Simple interface for loading JSON configs
11
11
  - **Validation**: Schema validation with detailed error messages
12
12
  - **Batch Operations**: Configure multiple devices from a single JSON file
@@ -29,6 +29,11 @@ For named pipe support (Windows only), you need:
29
29
  pip install pywin32
30
30
  ```
31
31
 
32
+ For `service_http` support (talking to a running BL_Service instance over HTTP, any platform), you need:
33
+ ```bash
34
+ pip install requests
35
+ ```
36
+
32
37
  ## Usage
33
38
 
34
39
  ### Interactive Mode
@@ -55,6 +60,11 @@ python -m benchlab -config --list
55
60
 
56
61
  # List devices via named pipe (Windows only)
57
62
  python -m benchlab -config --list --source named_pipe
63
+
64
+ # List devices via the BL_Service HTTP API
65
+ python -m benchlab -config --list --source service_http
66
+ python -m benchlab -config --list --source service_http \
67
+ --service-url http://localhost:8585 --service-token secret
58
68
  ```
59
69
 
60
70
  #### Export Configuration
@@ -68,6 +78,9 @@ python -m benchlab -config --export my_config.json --device COM4
68
78
 
69
79
  # Export via named pipe
70
80
  python -m benchlab -config --export my_config.json --source named_pipe
81
+
82
+ # Export via the BL_Service HTTP API
83
+ python -m benchlab -config --export my_config.json --source service_http
71
84
  ```
72
85
 
73
86
  #### Import Configuration
@@ -88,6 +101,9 @@ python -m benchlab -config --import my_config.json --yes
88
101
 
89
102
  # Import via named pipe
90
103
  python -m benchlab -config --import my_config.json --source named_pipe
104
+
105
+ # Import via the BL_Service HTTP API
106
+ python -m benchlab -config --import my_config.json --source service_http
91
107
  ```
92
108
 
93
109
  ## JSON Configuration Format
@@ -120,6 +136,7 @@ python -m benchlab -config --import my_config.json --source named_pipe
120
136
  - **`port`**: Match by serial port (e.g., "COM4" - not portable)
121
137
  - **`productId`**: Match by product ID (0x10 or 0x11)
122
138
  - **`pipeName`**: Match by named pipe name (named_pipe source only)
139
+ - **`guid`** is also required when using `service_http`, since the service addresses devices by their UID rather than a port or pipe name.
123
140
 
124
141
  **Note:** When exporting, the tool automatically uses `guid` selector with the device UID for portability. This allows you to export configuration on one system and import it on another, and the tool will find the same device regardless of which COM port it's connected to.
125
142
 
@@ -235,6 +252,25 @@ Uses the Windows BL_Service named pipe interface for communication.
235
252
  - Requires BL_Service to be running
236
253
  - Requires pywin32 library
237
254
 
255
+ ### Service HTTP (BL_Service HTTP API)
256
+
257
+ Talks to a running BL_Service instance over its HTTP API (`--service-url`,
258
+ default `http://localhost:8585`). Fan, RGB, and calibration read/write go
259
+ through a translation layer (`benchlab/config/http_dto.py`) between the
260
+ service's DTO JSON shapes and this tool's own config format — see that
261
+ module if you're integrating against the HTTP API directly rather than
262
+ through this tool.
263
+
264
+ **Advantages:**
265
+ - Cross-platform (the service itself may run anywhere reachable over HTTP)
266
+ - Multiple tools can access the device simultaneously
267
+ - No direct serial port management
268
+
269
+ **Limitations:**
270
+ - Requires BL_Service to be running and reachable
271
+ - Requires the `requests` library
272
+ - Device selector must use `guid` (the service addresses devices by UID, not port/pipe name)
273
+
238
274
  ## Configuration Profiles
239
275
 
240
276
  ### Profile IDs
@@ -276,6 +312,11 @@ Use `--dry-run` to preview exactly what would change on the device (connects and
276
312
  - Check Windows service status
277
313
  - Verify device is connected to service
278
314
 
315
+ **Service HTTP:**
316
+ - Ensure BL_Service is running and reachable at `--service-url`
317
+ - Check `--service-token` if the service has token auth enabled
318
+ - Verify the device UID is correct (use `--list` to see available UIDs)
319
+
279
320
  ### Import Fails
280
321
 
281
322
  - Use `--dry-run` to validate JSON syntax
@@ -316,12 +357,14 @@ manager.import_config('input.json', dry_run=False)
316
357
  ```
317
358
 
318
359
  For lower-level access (used internally by `ConfigManager`), `create_config_client()`
319
- returns a `ConfigClient` with `read_calibration()`/`write_calibration()`,
360
+ returns a `ConfigClient` with `read_fan_config()`/`write_fan_config()`,
361
+ `read_rgb_config()`/`write_rgb_config()`, `read_calibration()`/`write_calibration()`,
320
362
  `save_config()`/`load_config()`/`reset_config()`, and (BL2 firmware 7+ only)
321
363
  `factory_cal_unlock()`, which temporarily lifts write-protection on the factory
322
- calibration slot. There is no CLI/JSON-config surface for `factory_cal_unlock()` —
323
- it's a niche factory-calibration tool, not a user-facing config option, and a
324
- successful call resets the device immediately:
364
+ calibration slot. All three sources (`direct`, `named_pipe`, `service_http`)
365
+ implement the full interface. There is no CLI/JSON-config surface for
366
+ `factory_cal_unlock()` — it's a niche factory-calibration tool, not a
367
+ user-facing config option, and a successful call resets the device immediately:
325
368
 
326
369
  ```python
327
370
  from benchlab.config.config_client import create_config_client
@@ -10,6 +10,12 @@ import logging
10
10
  from abc import ABC, abstractmethod
11
11
  from typing import Optional, Dict, Any
12
12
 
13
+ from .http_dto import (
14
+ fan_dto_to_dict, fan_dict_to_dto,
15
+ rgb_dto_to_dict, rgb_dict_to_dto,
16
+ calibration_dto_to_dict, calibration_dict_to_dto,
17
+ )
18
+
13
19
  logger = logging.getLogger("benchlab.config.client")
14
20
 
15
21
  DISCOVERY_PIPE_NAME = "BenchlabDiscovery"
@@ -160,6 +166,18 @@ class ConfigClient(ABC):
160
166
  """
161
167
  pass
162
168
 
169
+ @abstractmethod
170
+ def enter_bootloader(self) -> bool:
171
+ """Jump the device into its USB DFU bootloader.
172
+
173
+ Disruptive: the device stops responding as a sensor/CDC device
174
+ immediately after this succeeds, and re-enumerates as USB DFU
175
+ (VID 0x0483 / PID 0xDF11). Callers must close this connection and
176
+ take over the device directly over USB from there -- see
177
+ benchlab.flash.flash_manager.
178
+ """
179
+ pass
180
+
163
181
  @abstractmethod
164
182
  def close(self):
165
183
  """Close connection and cleanup resources."""
@@ -473,6 +491,16 @@ class DirectConfigClient(ConfigClient):
473
491
  return False
474
492
  return factory_cal_unlock(self.ser)
475
493
 
494
+ def enter_bootloader(self) -> bool:
495
+ """Jump the device into its USB DFU bootloader.
496
+
497
+ Disruptive: the device stops responding over CDC immediately after
498
+ this succeeds. Mirrors enter_bootloader()'s own caveat in pycore --
499
+ the actual jump happens on the firmware's next 100ms task tick.
500
+ """
501
+ from benchlab_pycore.core.config_io import enter_bootloader
502
+ return enter_bootloader(self.ser)
503
+
476
504
  def close(self):
477
505
  """Close serial connection."""
478
506
  if self.ser and self.ser.is_open:
@@ -657,18 +685,218 @@ class NamedPipeConfigClient(ConfigClient):
657
685
  result = self._send_command("SendFactoryCalUnlock", payload="benchlab")
658
686
  return result and result.get('success', False)
659
687
 
688
+ def enter_bootloader(self) -> bool:
689
+ """Jump the device into its USB DFU bootloader.
690
+
691
+ Disruptive: the device stops responding over CDC immediately after
692
+ this succeeds, and the C# service marks it DISCONNECTED (it does
693
+ not treat this as a device error). Mirrors the service's own
694
+ SendBootloader pipe command and its HTTP equivalent
695
+ (POST /device/{uid}/bootloader).
696
+ """
697
+ result = self._send_command("SendBootloader")
698
+ return result and result.get('success', False)
699
+
660
700
  def close(self):
661
701
  """Close pipe connection."""
662
702
  self._close_pipe()
663
703
  logger.info(f"Closed named pipe connection to {self.pipe_name}")
664
704
 
665
705
 
666
- def create_config_client(source: str, identifier: str) -> ConfigClient:
706
+ class HttpConfigClient(ConfigClient):
707
+ """Configuration client using the C# BenchLab service's HTTP API.
708
+
709
+ Implements device info, name, save/load config, fan/RGB/calibration
710
+ read-write, and the disruptive reset/bootloader/factory-cal-unlock
711
+ commands. Fan/RGB/calibration routes (GET/PUT /device/{uid}/fan,
712
+ /rgb/{profile}, /calibration) use DTOs with a different, more
713
+ structured shape than this repo's raw ctypes-struct-dict convention
714
+ (DirectConfigClient/NamedPipeConfigClient) -- see benchlab.config.
715
+ http_dto for the translation layer between the two. reset_config has
716
+ no HTTP equivalent at all (by design: the service only exposes
717
+ save/load actions, since no firmware contract defines a config-reset
718
+ opcode -- RESET power-cycles the whole device instead).
719
+ """
720
+
721
+ DEFAULT_URL = "http://localhost:8585"
722
+
723
+ def __init__(self, uid: str, base_url: str = DEFAULT_URL,
724
+ token: Optional[str] = None, timeout: float = 5.0):
725
+ """Initialize an HTTP client scoped to one device.
726
+
727
+ Args:
728
+ uid: Device UID (used in /device/{uid}/... routes).
729
+ base_url: Base URL of the C# BenchLab service.
730
+ token: Optional X-Benchlab-Token value (only needed when the
731
+ service has token auth configured -- required on every
732
+ request except GET /health).
733
+ timeout: HTTP request timeout in seconds.
734
+ """
735
+ import requests
736
+
737
+ self.uid = uid
738
+ self.base_url = base_url.rstrip("/")
739
+ self.timeout = timeout
740
+ self._session = requests.Session()
741
+ if token:
742
+ self._session.headers["X-Benchlab-Token"] = token
743
+
744
+ def _get(self, path: str) -> Optional[Dict[str, Any]]:
745
+ try:
746
+ resp = self._session.get(
747
+ f"{self.base_url}{path}", timeout=self.timeout)
748
+ if resp.status_code == 200:
749
+ return resp.json()
750
+ logger.warning(
751
+ "GET %s -> %s: %s", path, resp.status_code, resp.text)
752
+ return None
753
+ except Exception as e:
754
+ logger.error(f"GET {path} failed: {e}")
755
+ return None
756
+
757
+ def _post(self, path: str, json_body=None) -> bool:
758
+ try:
759
+ resp = self._session.post(
760
+ f"{self.base_url}{path}", json=json_body,
761
+ timeout=self.timeout)
762
+ if resp.status_code in (200, 204):
763
+ return True
764
+ logger.warning(
765
+ "POST %s -> %s: %s", path, resp.status_code, resp.text)
766
+ return False
767
+ except Exception as e:
768
+ logger.error(f"POST {path} failed: {e}")
769
+ return False
770
+
771
+ def _put(self, path: str, json_body=None) -> bool:
772
+ try:
773
+ resp = self._session.put(
774
+ f"{self.base_url}{path}", json=json_body,
775
+ timeout=self.timeout)
776
+ if resp.status_code in (200, 204):
777
+ return True
778
+ logger.warning(
779
+ "PUT %s -> %s: %s", path, resp.status_code, resp.text)
780
+ return False
781
+ except Exception as e:
782
+ logger.error(f"PUT {path} failed: {e}")
783
+ return False
784
+
785
+ def get_device_info(self) -> Optional[Dict[str, Any]]:
786
+ """Get device information from GET /device/{uid}/info."""
787
+ return self._get(f"/device/{self.uid}/info")
788
+
789
+ def read_device_name(self) -> Optional[str]:
790
+ info = self.get_device_info()
791
+ return info.get('deviceName') if info else None
792
+
793
+ def write_device_name(self, name: str) -> bool:
794
+ """Write device friendly name via PUT /device/{uid}/name."""
795
+ return self._put(f"/device/{self.uid}/name", {"name": name})
796
+
797
+ def read_fan_config(self, profile_id: int,
798
+ fan_id: int) -> Optional[Dict[str, Any]]:
799
+ """Read fan config via GET /device/{uid}/fan/{profile}/{fan}."""
800
+ dto = self._get(f"/device/{self.uid}/fan/{profile_id}/{fan_id}")
801
+ return fan_dto_to_dict(dto) if dto is not None else None
802
+
803
+ def write_fan_config(self, profile_id: int, fan_id: int,
804
+ config: Dict[str, Any]) -> bool:
805
+ """Write fan config via PUT /device/{uid}/fan/{profile}/{fan}."""
806
+ return self._put(
807
+ f"/device/{self.uid}/fan/{profile_id}/{fan_id}",
808
+ fan_dict_to_dto(config))
809
+
810
+ def read_rgb_config(self, profile_id: int) -> Optional[Dict[str, Any]]:
811
+ """Read RGB config via GET /device/{uid}/rgb/{profile}."""
812
+ dto = self._get(f"/device/{self.uid}/rgb/{profile_id}")
813
+ return rgb_dto_to_dict(dto) if dto is not None else None
814
+
815
+ def write_rgb_config(self, profile_id: int,
816
+ config: Dict[str, Any]) -> bool:
817
+ """Write RGB config via PUT /device/{uid}/rgb/{profile}."""
818
+ return self._put(
819
+ f"/device/{self.uid}/rgb/{profile_id}", rgb_dict_to_dto(config))
820
+
821
+ def read_calibration(self) -> Optional[Dict[str, Any]]:
822
+ """Read calibration via GET /device/{uid}/calibration.
823
+
824
+ Works for both BENCHLAB Original and BL2 -- the service resolves
825
+ the connected device's variant server-side and returns a
826
+ CalibrationDto with array lengths matching that variant.
827
+ """
828
+ dto = self._get(f"/device/{self.uid}/calibration")
829
+ return calibration_dto_to_dict(dto) if dto is not None else None
830
+
831
+ def write_calibration(self, calibration: Dict[str, Any]) -> bool:
832
+ """Write calibration via PUT /device/{uid}/calibration.
833
+
834
+ No read-before-write: the server validates the submitted array
835
+ lengths (Vin/Ts/TsB/power arrays) against the connected device's
836
+ own variant and rejects a mismatch with a 400 (surfaced here as a
837
+ logged warning and a False return). Callers are expected to pass
838
+ a dict shaped like read_calibration()'s own output. The submitted
839
+ Crc is ignored -- the server always recomputes it before writing.
840
+ """
841
+ return self._put(
842
+ f"/device/{self.uid}/calibration",
843
+ calibration_dict_to_dto(calibration))
844
+
845
+ def save_config(self) -> bool:
846
+ """Save configuration to device flash via
847
+ POST /device/{uid}/config/save."""
848
+ return self._post(f"/device/{self.uid}/config/save")
849
+
850
+ def load_config(self) -> bool:
851
+ """Reload configuration from device flash via
852
+ POST /device/{uid}/config/load."""
853
+ return self._post(f"/device/{self.uid}/config/load")
854
+
855
+ def reset_config(self) -> bool:
856
+ raise NotImplementedError(
857
+ "HttpConfigClient does not support config reset -- the C# "
858
+ "service has no HTTP route for it, by design: no firmware "
859
+ "contract defines a config-reset opcode (use the device's "
860
+ "RESET command / POST /device/{uid}/reset to power-cycle "
861
+ "instead, which is not equivalent)")
862
+
863
+ def factory_cal_unlock(self) -> bool:
864
+ """Temporarily lift write-protection on BL2's factory calibration
865
+ slot via POST /device/{uid}/factory-cal-unlock.
866
+
867
+ Disruptive: a successful call resets the device immediately -- the
868
+ service marks it DISCONNECTED until it re-enumerates.
869
+ """
870
+ return self._post(
871
+ f"/device/{self.uid}/factory-cal-unlock",
872
+ json_body={"passphrase": "benchlab"})
873
+
874
+ def enter_bootloader(self) -> bool:
875
+ """Jump the device into its USB DFU bootloader via
876
+ POST /device/{uid}/bootloader.
877
+
878
+ Disruptive: the device stops responding over CDC immediately after
879
+ this succeeds, and the C# service marks it DISCONNECTED (it does
880
+ not treat this as a device error).
881
+ """
882
+ return self._post(f"/device/{self.uid}/bootloader")
883
+
884
+ def close(self):
885
+ """Close the HTTP session."""
886
+ self._session.close()
887
+
888
+
889
+ def create_config_client(
890
+ source: str, identifier: str, base_url: Optional[str] = None,
891
+ token: Optional[str] = None) -> ConfigClient:
667
892
  """Factory function to create appropriate config client.
668
893
 
669
894
  Args:
670
- source: 'direct' or 'named_pipe'
671
- identifier: Port name for direct, pipe name for named_pipe
895
+ source: 'direct', 'named_pipe', or 'service_http'
896
+ identifier: Port name for direct, pipe name for named_pipe, device
897
+ UID for service_http
898
+ base_url: Service base URL (service_http only)
899
+ token: Optional X-Benchlab-Token (service_http only)
672
900
 
673
901
  Returns:
674
902
  ConfigClient instance
@@ -681,5 +909,12 @@ def create_config_client(source: str, identifier: str) -> ConfigClient:
681
909
  return DirectConfigClient(identifier)
682
910
  elif source == 'named_pipe':
683
911
  return NamedPipeConfigClient(identifier)
912
+ elif source == 'service_http':
913
+ kwargs = {}
914
+ if base_url:
915
+ kwargs['base_url'] = base_url
916
+ if token:
917
+ kwargs['token'] = token
918
+ return HttpConfigClient(identifier, **kwargs)
684
919
  else:
685
920
  raise ValueError(f"Invalid source type: {source}")