benchlab-pytools 3.0.0__py3-none-any.whl

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 (184) hide show
  1. benchlab/__init__.py +14 -0
  2. benchlab/__main__.py +21 -0
  3. benchlab/config/README.md +325 -0
  4. benchlab/config/__init__.py +9 -0
  5. benchlab/config/config_client.py +631 -0
  6. benchlab/config/config_manager.py +495 -0
  7. benchlab/config/config_tool.py +358 -0
  8. benchlab/config/diff.py +163 -0
  9. benchlab/config/examples/complete_config.json +56 -0
  10. benchlab/config/examples/rgb_lighting.json +24 -0
  11. benchlab/config/examples/simple_fan.json +32 -0
  12. benchlab/config/schema.py +138 -0
  13. benchlab/core/README.md +154 -0
  14. benchlab/core/__init__.py +78 -0
  15. benchlab/core/config.py +145 -0
  16. benchlab/core/datasource.py +1209 -0
  17. benchlab/core/datasource_manager.py +418 -0
  18. benchlab/core/device_registry.py +206 -0
  19. benchlab/core/discovery.py +92 -0
  20. benchlab/core/infrastructure.py +309 -0
  21. benchlab/core/process_manager.py +356 -0
  22. benchlab/core/retry.py +85 -0
  23. benchlab/core/shared_serial.py +235 -0
  24. benchlab/core/statistics.py +224 -0
  25. benchlab/csv_log/README.md +206 -0
  26. benchlab/csv_log/__init__.py +5 -0
  27. benchlab/csv_log/csv_logger_enhanced.py +416 -0
  28. benchlab/csv_log/message_batcher.py +361 -0
  29. benchlab/csv_log/requirements.txt +3 -0
  30. benchlab/csv_log/smart_retry.py +466 -0
  31. benchlab/graph/README.md +117 -0
  32. benchlab/graph/__init__.py +5 -0
  33. benchlab/graph/app.py +353 -0
  34. benchlab/graph/requirements.txt +2 -0
  35. benchlab/graph/runner.py +42 -0
  36. benchlab/graph/ui.py +288 -0
  37. benchlab/hwinfo/README.md +135 -0
  38. benchlab/hwinfo/__init__.py +5 -0
  39. benchlab/hwinfo/hwinfo_export.py +386 -0
  40. benchlab/hwinfo/requirements.txt +1 -0
  41. benchlab/launcher.py +387 -0
  42. benchlab/link/.env.EXAMPLE +10 -0
  43. benchlab/link/README.md +172 -0
  44. benchlab/link/__init__.py +1 -0
  45. benchlab/link/link_main.py +479 -0
  46. benchlab/main.py +462 -0
  47. benchlab/menu.py +729 -0
  48. benchlab/menu_classic.py +410 -0
  49. benchlab/mqtt/README.md +201 -0
  50. benchlab/mqtt/__init__.py +5 -0
  51. benchlab/mqtt/mqtt.config_template +2 -0
  52. benchlab/mqtt/mqtt_publisher.py +540 -0
  53. benchlab/mqtt/requirements.txt +3 -0
  54. benchlab/prefs.py +67 -0
  55. benchlab/restapi/.env.example +17 -0
  56. benchlab/restapi/__init__.py +5 -0
  57. benchlab/restapi/favicon.ico +0 -0
  58. benchlab/restapi/readme.md +202 -0
  59. benchlab/restapi/requirements.txt +5 -0
  60. benchlab/restapi/telemetry_api.py +808 -0
  61. benchlab/sources.py +515 -0
  62. benchlab/tools.py +147 -0
  63. benchlab/tui/README.md +266 -0
  64. benchlab/tui/__init__.py +5 -0
  65. benchlab/tui/config.py +477 -0
  66. benchlab/tui/requirements.txt +2 -0
  67. benchlab/tui/tui_core.py +1298 -0
  68. benchlab/tui/tui_main.py +418 -0
  69. benchlab/vu/README.md +64 -0
  70. benchlab/vu/VU-Server/NOTICE.md +13 -0
  71. benchlab/vu/VU-Server/README.md +80 -0
  72. benchlab/vu/VU-Server/Running_from_source_on_linux.md +97 -0
  73. benchlab/vu/VU-Server/__init__.py +0 -0
  74. benchlab/vu/VU-Server/assets/vu1_hello_world.png +0 -0
  75. benchlab/vu/VU-Server/community_applications.md +13 -0
  76. benchlab/vu/VU-Server/config.yaml +9 -0
  77. benchlab/vu/VU-Server/database.py +256 -0
  78. benchlab/vu/VU-Server/dial_driver.py +534 -0
  79. benchlab/vu/VU-Server/dials/Comms_Hub_Gauge.py +78 -0
  80. benchlab/vu/VU-Server/dials/Comms_Hub_Server.py +100 -0
  81. benchlab/vu/VU-Server/dials/__init__.py +0 -0
  82. benchlab/vu/VU-Server/dials/base_logger.py +100 -0
  83. benchlab/vu/VU-Server/installer/inc/icon.ico +0 -0
  84. benchlab/vu/VU-Server/installer/install.nsi +148 -0
  85. benchlab/vu/VU-Server/make_version.py +40 -0
  86. benchlab/vu/VU-Server/pylintrc +628 -0
  87. benchlab/vu/VU-Server/requirements.txt +9 -0
  88. benchlab/vu/VU-Server/serial_driver.py +244 -0
  89. benchlab/vu/VU-Server/server.py +670 -0
  90. benchlab/vu/VU-Server/server_config.py +255 -0
  91. benchlab/vu/VU-Server/server_dial_handler.py +334 -0
  92. benchlab/vu/VU-Server/vu_notifications.py +24 -0
  93. benchlab/vu/VU-Server/www/android-chrome-192x192.png +0 -0
  94. benchlab/vu/VU-Server/www/android-chrome-512x512.png +0 -0
  95. benchlab/vu/VU-Server/www/apple-touch-icon.png +0 -0
  96. benchlab/vu/VU-Server/www/assets/css/demo.css +291 -0
  97. benchlab/vu/VU-Server/www/assets/css/demo.min.css +9 -0
  98. benchlab/vu/VU-Server/www/assets/css/demo.rtl.css +276 -0
  99. benchlab/vu/VU-Server/www/assets/css/demo.rtl.min.css +9 -0
  100. benchlab/vu/VU-Server/www/assets/css/tabler-flags.css +1084 -0
  101. benchlab/vu/VU-Server/www/assets/css/tabler-flags.min.css +9 -0
  102. benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.css +1084 -0
  103. benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.min.css +9 -0
  104. benchlab/vu/VU-Server/www/assets/css/tabler-payments.css +528 -0
  105. benchlab/vu/VU-Server/www/assets/css/tabler-payments.min.css +9 -0
  106. benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.css +528 -0
  107. benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.min.css +9 -0
  108. benchlab/vu/VU-Server/www/assets/css/tabler-social.css +10 -0
  109. benchlab/vu/VU-Server/www/assets/css/tabler-social.min.css +8 -0
  110. benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.css +10 -0
  111. benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.min.css +8 -0
  112. benchlab/vu/VU-Server/www/assets/css/tabler-vendors.css +1504 -0
  113. benchlab/vu/VU-Server/www/assets/css/tabler-vendors.min.css +9 -0
  114. benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.css +1504 -0
  115. benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.min.css +9 -0
  116. benchlab/vu/VU-Server/www/assets/css/tabler.css +25813 -0
  117. benchlab/vu/VU-Server/www/assets/css/tabler.min.css +13 -0
  118. benchlab/vu/VU-Server/www/assets/css/tabler.rtl.css +25777 -0
  119. benchlab/vu/VU-Server/www/assets/css/tabler.rtl.min.css +13 -0
  120. benchlab/vu/VU-Server/www/assets/css/theme.min.css +261 -0
  121. benchlab/vu/VU-Server/www/assets/img/header.png +0 -0
  122. benchlab/vu/VU-Server/www/assets/img/logo.png +0 -0
  123. benchlab/vu/VU-Server/www/assets/js/dark-theme.js +35 -0
  124. benchlab/vu/VU-Server/www/assets/js/dark-theme.min.js +9 -0
  125. benchlab/vu/VU-Server/www/assets/js/demo.js +132 -0
  126. benchlab/vu/VU-Server/www/assets/js/demo.min.js +9 -0
  127. benchlab/vu/VU-Server/www/assets/js/jquery.min.js +2 -0
  128. benchlab/vu/VU-Server/www/assets/js/tabler.esm.js +7482 -0
  129. benchlab/vu/VU-Server/www/assets/js/tabler.esm.min.js +15 -0
  130. benchlab/vu/VU-Server/www/assets/js/tabler.js +7543 -0
  131. benchlab/vu/VU-Server/www/assets/js/tabler.min.js +15 -0
  132. benchlab/vu/VU-Server/www/assets/js/vu1_api.js +68 -0
  133. benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_key_settings.js +167 -0
  134. benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_keys.js +138 -0
  135. benchlab/vu/VU-Server/www/assets/js/vu1_gui_dial.js +179 -0
  136. benchlab/vu/VU-Server/www/assets/js/vu1_gui_home.js +60 -0
  137. benchlab/vu/VU-Server/www/assets/js/vu1_gui_root.js +125 -0
  138. benchlab/vu/VU-Server/www/favicon-16x16.png +0 -0
  139. benchlab/vu/VU-Server/www/favicon-32x32.png +0 -0
  140. benchlab/vu/VU-Server/www/favicon.ico +0 -0
  141. benchlab/vu/VU-Server/www/favicon.png +0 -0
  142. benchlab/vu/VU-Server/www/index.html +47 -0
  143. benchlab/vu/VU-Server/www/views/api_keys.html +151 -0
  144. benchlab/vu/VU-Server/www/views/dial.html +286 -0
  145. benchlab/vu/VU-Server/www/views/footer.html +27 -0
  146. benchlab/vu/VU-Server/www/views/header.html +86 -0
  147. benchlab/vu/VU-Server/www/views/key_settings.html +132 -0
  148. benchlab/vu/VU-Server/www/views/start.html +124 -0
  149. benchlab/vu/__init__.py +5 -0
  150. benchlab/vu/assets/Barlow-Bold.ttf +0 -0
  151. benchlab/vu/assets/bl_dial_144x200.png +0 -0
  152. benchlab/vu/assets/bl_logo_144x200.png +0 -0
  153. benchlab/vu/devices.py +166 -0
  154. benchlab/vu/requirements.txt +24 -0
  155. benchlab/vu/sensors.py +18 -0
  156. benchlab/vu/vu_dial.config_template +90 -0
  157. benchlab/vu/vu_logo_gen.py +97 -0
  158. benchlab/vu/vu_server.config_template +5 -0
  159. benchlab/vu/vu_server_config.py +40 -0
  160. benchlab/vu/vu_server_manager.py +192 -0
  161. benchlab/vu/vu_tui.py +919 -0
  162. benchlab/vu/vu_updater.py +540 -0
  163. benchlab/wigidash/README.md +182 -0
  164. benchlab/wigidash/__init__.py +5 -0
  165. benchlab/wigidash/assets/Inter-Bold.ttf +0 -0
  166. benchlab/wigidash/assets/Inter.ttf +0 -0
  167. benchlab/wigidash/assets/benchlab.png +0 -0
  168. benchlab/wigidash/benchlab_fleet.py +150 -0
  169. benchlab/wigidash/benchlab_graph.py +517 -0
  170. benchlab/wigidash/benchlab_overview.py +426 -0
  171. benchlab/wigidash/benchlab_telemetry.py +105 -0
  172. benchlab/wigidash/benchlab_ui.py +190 -0
  173. benchlab/wigidash/benchlab_utils.py +182 -0
  174. benchlab/wigidash/requirements.txt +9 -0
  175. benchlab/wigidash/wigidash_device.py +541 -0
  176. benchlab/wigidash/wigidash_manager.py +342 -0
  177. benchlab/wigidash/wigidash_session.py +355 -0
  178. benchlab/wigidash/wigidash_usb.py +247 -0
  179. benchlab/wigidash/wigidash_widget.py +88 -0
  180. benchlab_pytools-3.0.0.dist-info/METADATA +239 -0
  181. benchlab_pytools-3.0.0.dist-info/RECORD +184 -0
  182. benchlab_pytools-3.0.0.dist-info/WHEEL +4 -0
  183. benchlab_pytools-3.0.0.dist-info/entry_points.txt +2 -0
  184. benchlab_pytools-3.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,40 @@
1
+ # benchlab/vu/vu_server_config.py
2
+
3
+ from pathlib import Path
4
+ import json
5
+
6
+ config_path = Path(__file__).parent / "vu_server.config"
7
+
8
+
9
+ def update_vu_config(dial_uid, dial_name, device, sensor):
10
+ # Load existing config
11
+ if config_path.exists():
12
+ with open(config_path, "r") as f:
13
+ config = json.load(f)
14
+ else:
15
+ config = {
16
+ "vu_server_url": "http://localhost:5340",
17
+ "api_key": "",
18
+ "mappings": [],
19
+ "update_interval_sec": 1}
20
+
21
+ # Remove any mapping with the same dial_uid
22
+ existing = config.get("mappings", [])
23
+ filtered = [m for m in existing if m.get("dial_uid") != dial_uid]
24
+
25
+ # Add new mapping
26
+ filtered.append({
27
+ "dial_uid": dial_uid,
28
+ "dial_name": dial_name,
29
+ "benchlab_uid": device["uid"],
30
+ "benchlab_port": device["port"],
31
+ "sensor": sensor
32
+ })
33
+
34
+ # Update config
35
+ config["mappings"] = filtered
36
+
37
+ # Write back to file
38
+ with open(config_path, "w") as f:
39
+ json.dump(config, f, indent=2)
40
+ print("[INFO] vu_server.config updated.")
@@ -0,0 +1,192 @@
1
+ import subprocess
2
+ import threading
3
+ import time
4
+ import requests
5
+ import yaml
6
+ import json
7
+ import signal
8
+ import logging
9
+ from pathlib import Path
10
+ import sys
11
+ import platform
12
+ import os
13
+
14
+ # -----------------------------------------------------------------------------
15
+ # Platform setup
16
+ # -----------------------------------------------------------------------------
17
+ IS_WINDOWS = platform.system() == "Windows"
18
+ PYTHON_CMD = sys.executable
19
+ CREATIONFLAGS = subprocess.CREATE_NEW_PROCESS_GROUP if IS_WINDOWS else 0
20
+
21
+ # -----------------------------------------------------------------------------
22
+ # Paths
23
+ # -----------------------------------------------------------------------------
24
+ BASE_DIR = Path(__file__).parent
25
+ VU_SERVER_DIR = BASE_DIR / "VU-Server"
26
+ VU_SERVER_CONFIG = BASE_DIR / "vu_server.config"
27
+ SERVER_YAML_CONFIG = VU_SERVER_DIR / "config.yaml"
28
+
29
+ # -----------------------------------------------------------------------------
30
+ # Logging
31
+ # -----------------------------------------------------------------------------
32
+ logger = logging.getLogger("vu_server_manager")
33
+
34
+ # -----------------------------------------------------------------------------
35
+ # Helpers
36
+ # -----------------------------------------------------------------------------
37
+
38
+
39
+ def check_vu_server(server_url: str, api_key: str = "") -> bool:
40
+ """Return True if the VU server responds successfully.
41
+
42
+ The server reads the API key as a `key` query parameter (see
43
+ server.py's BaseHandler.is_valid_api_key), not an HTTP header — match
44
+ that here so a correct api_key is actually validated by the server
45
+ instead of always being rejected and falling through to the 403
46
+ "still running" case below.
47
+ """
48
+ try:
49
+ # "localhost" resolves to both an IPv4 and IPv6 address on Windows,
50
+ # and requests/urllib3 tries each in turn, each getting its own
51
+ # full timeout budget — a "nothing listening" check with
52
+ # timeout=1 can silently take ~2s instead of 1s. Force IPv4
53
+ # loopback for the common "localhost" case so the timeout below
54
+ # is actually honored; leave any other configured hostname alone.
55
+ probe_url = server_url.replace("://localhost", "://127.0.0.1", 1)
56
+ params = {"key": api_key} if api_key else {}
57
+ r = requests.get(
58
+ f"{probe_url}/api/v0/dial/list",
59
+ params=params,
60
+ timeout=1)
61
+ # 403 (missing/invalid key) still means a real VU server answered —
62
+ # that's enough to know we shouldn't auto-start a second one.
63
+ return r.status_code in (200, 403)
64
+ except requests.RequestException:
65
+ return False
66
+
67
+
68
+ def forward_logs(proc: subprocess.Popen):
69
+ """Forward VU server stdout to the main logger."""
70
+ if not proc.stdout:
71
+ logger.warning("No stdout to forward from VU server.")
72
+ return
73
+ for line in proc.stdout:
74
+ if line := line.rstrip():
75
+ logger.info(f"[VU SERVER] {line}")
76
+
77
+ # -----------------------------------------------------------------------------
78
+ # Server startup
79
+ # -----------------------------------------------------------------------------
80
+
81
+
82
+ def start_vu_server() -> subprocess.Popen | None:
83
+ """
84
+ Ensure a VU server is running.
85
+ Returns a subprocess.Popen handle if a new server was started, or
86
+ None if already running.
87
+ """
88
+ logger.info("Checking for existing VU server...")
89
+
90
+ # Load previous JSON config
91
+ server_cfg = {}
92
+ if VU_SERVER_CONFIG.exists():
93
+ try:
94
+ server_cfg = json.loads(VU_SERVER_CONFIG.read_text())
95
+ except Exception as e:
96
+ logger.warning(f"Failed to read {VU_SERVER_CONFIG}: {e}")
97
+
98
+ url = server_cfg.get("vu_server_url", "http://localhost:5340")
99
+ api_key = server_cfg.get("api_key", "")
100
+
101
+ if check_vu_server(url, api_key):
102
+ logger.info(f"VU server already running at {url}")
103
+ return None
104
+
105
+ logger.info("No VU server found — starting one...")
106
+
107
+ # Load YAML config
108
+ try:
109
+ cfg = yaml.safe_load(SERVER_YAML_CONFIG.read_text())
110
+ except Exception as e:
111
+ logger.error(f"Failed to read {SERVER_YAML_CONFIG}: {e}")
112
+ raise
113
+
114
+ host = cfg.get("server", {}).get("hostname", "localhost")
115
+ port = cfg.get("server", {}).get("port", 5340)
116
+ master_key = cfg.get("server", {}).get("master_key", "")
117
+
118
+ new_cfg = {
119
+ "vu_server_url": f"http://{host}:{port}",
120
+ "api_key": master_key,
121
+ "logo_file": str(
122
+ server_cfg.get(
123
+ "logo_file",
124
+ "assets/bl_logo_144x200.png"))}
125
+
126
+ # Ensure VU-Server directory exists
127
+ if not VU_SERVER_DIR.exists():
128
+ logger.error(f"Missing server directory: {VU_SERVER_DIR}")
129
+ raise FileNotFoundError(VU_SERVER_DIR)
130
+
131
+ # Launch server
132
+ proc = subprocess.Popen(
133
+ [PYTHON_CMD, "-u", str(VU_SERVER_DIR / "server.py")],
134
+ cwd=str(VU_SERVER_DIR),
135
+ stdout=subprocess.PIPE,
136
+ stderr=subprocess.STDOUT,
137
+ bufsize=1,
138
+ text=True,
139
+ creationflags=CREATIONFLAGS,
140
+ preexec_fn=os.setsid if not IS_WINDOWS else None,
141
+ )
142
+
143
+ threading.Thread(target=forward_logs, args=(proc,), daemon=True).start()
144
+
145
+ # Wait for server to become ready
146
+ for _ in range(10):
147
+ time.sleep(1)
148
+ if check_vu_server(new_cfg["vu_server_url"], new_cfg["api_key"]):
149
+ logger.info(
150
+ f"VU server is now running at {
151
+ new_cfg['vu_server_url']}")
152
+ break
153
+ else:
154
+ logger.error("Failed to verify VU server after startup.")
155
+ return None
156
+
157
+ # Only persist the new config once the server is confirmed up, so a
158
+ # failed launch doesn't leave vu_server.config pointing at nothing.
159
+ try:
160
+ VU_SERVER_CONFIG.write_text(json.dumps(new_cfg, indent=2))
161
+ logger.info(
162
+ f"Updated {VU_SERVER_CONFIG} with {
163
+ new_cfg['vu_server_url']}")
164
+ except Exception as e:
165
+ logger.warning(f"Failed to write {VU_SERVER_CONFIG}: {e}")
166
+
167
+ return proc
168
+
169
+ # -----------------------------------------------------------------------------
170
+ # Shutdown
171
+ # -----------------------------------------------------------------------------
172
+
173
+
174
+ def terminate_vu_server(proc: subprocess.Popen | None):
175
+ if not proc or proc.poll() is not None:
176
+ return
177
+ logger.info("Terminating auto-started VU server...")
178
+ try:
179
+ if IS_WINDOWS:
180
+ proc.send_signal(signal.CTRL_BREAK_EVENT)
181
+ else:
182
+ os.killpg(os.getpgid(proc.pid), signal.SIGINT)
183
+ proc.wait(timeout=5)
184
+ except subprocess.TimeoutExpired:
185
+ logger.warning("VU server did not exit gracefully — force killing.")
186
+ try:
187
+ proc.kill()
188
+ proc.wait(timeout=5)
189
+ except Exception as e:
190
+ logger.warning(f"Failed to force-kill VU server: {e}")
191
+ except Exception as e:
192
+ logger.warning(f"Failed to terminate VU server cleanly: {e}")