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.
- benchlab/__init__.py +14 -0
- benchlab/__main__.py +21 -0
- benchlab/config/README.md +325 -0
- benchlab/config/__init__.py +9 -0
- benchlab/config/config_client.py +631 -0
- benchlab/config/config_manager.py +495 -0
- benchlab/config/config_tool.py +358 -0
- benchlab/config/diff.py +163 -0
- benchlab/config/examples/complete_config.json +56 -0
- benchlab/config/examples/rgb_lighting.json +24 -0
- benchlab/config/examples/simple_fan.json +32 -0
- benchlab/config/schema.py +138 -0
- benchlab/core/README.md +154 -0
- benchlab/core/__init__.py +78 -0
- benchlab/core/config.py +145 -0
- benchlab/core/datasource.py +1209 -0
- benchlab/core/datasource_manager.py +418 -0
- benchlab/core/device_registry.py +206 -0
- benchlab/core/discovery.py +92 -0
- benchlab/core/infrastructure.py +309 -0
- benchlab/core/process_manager.py +356 -0
- benchlab/core/retry.py +85 -0
- benchlab/core/shared_serial.py +235 -0
- benchlab/core/statistics.py +224 -0
- benchlab/csv_log/README.md +206 -0
- benchlab/csv_log/__init__.py +5 -0
- benchlab/csv_log/csv_logger_enhanced.py +416 -0
- benchlab/csv_log/message_batcher.py +361 -0
- benchlab/csv_log/requirements.txt +3 -0
- benchlab/csv_log/smart_retry.py +466 -0
- benchlab/graph/README.md +117 -0
- benchlab/graph/__init__.py +5 -0
- benchlab/graph/app.py +353 -0
- benchlab/graph/requirements.txt +2 -0
- benchlab/graph/runner.py +42 -0
- benchlab/graph/ui.py +288 -0
- benchlab/hwinfo/README.md +135 -0
- benchlab/hwinfo/__init__.py +5 -0
- benchlab/hwinfo/hwinfo_export.py +386 -0
- benchlab/hwinfo/requirements.txt +1 -0
- benchlab/launcher.py +387 -0
- benchlab/link/.env.EXAMPLE +10 -0
- benchlab/link/README.md +172 -0
- benchlab/link/__init__.py +1 -0
- benchlab/link/link_main.py +479 -0
- benchlab/main.py +462 -0
- benchlab/menu.py +729 -0
- benchlab/menu_classic.py +410 -0
- benchlab/mqtt/README.md +201 -0
- benchlab/mqtt/__init__.py +5 -0
- benchlab/mqtt/mqtt.config_template +2 -0
- benchlab/mqtt/mqtt_publisher.py +540 -0
- benchlab/mqtt/requirements.txt +3 -0
- benchlab/prefs.py +67 -0
- benchlab/restapi/.env.example +17 -0
- benchlab/restapi/__init__.py +5 -0
- benchlab/restapi/favicon.ico +0 -0
- benchlab/restapi/readme.md +202 -0
- benchlab/restapi/requirements.txt +5 -0
- benchlab/restapi/telemetry_api.py +808 -0
- benchlab/sources.py +515 -0
- benchlab/tools.py +147 -0
- benchlab/tui/README.md +266 -0
- benchlab/tui/__init__.py +5 -0
- benchlab/tui/config.py +477 -0
- benchlab/tui/requirements.txt +2 -0
- benchlab/tui/tui_core.py +1298 -0
- benchlab/tui/tui_main.py +418 -0
- benchlab/vu/README.md +64 -0
- benchlab/vu/VU-Server/NOTICE.md +13 -0
- benchlab/vu/VU-Server/README.md +80 -0
- benchlab/vu/VU-Server/Running_from_source_on_linux.md +97 -0
- benchlab/vu/VU-Server/__init__.py +0 -0
- benchlab/vu/VU-Server/assets/vu1_hello_world.png +0 -0
- benchlab/vu/VU-Server/community_applications.md +13 -0
- benchlab/vu/VU-Server/config.yaml +9 -0
- benchlab/vu/VU-Server/database.py +256 -0
- benchlab/vu/VU-Server/dial_driver.py +534 -0
- benchlab/vu/VU-Server/dials/Comms_Hub_Gauge.py +78 -0
- benchlab/vu/VU-Server/dials/Comms_Hub_Server.py +100 -0
- benchlab/vu/VU-Server/dials/__init__.py +0 -0
- benchlab/vu/VU-Server/dials/base_logger.py +100 -0
- benchlab/vu/VU-Server/installer/inc/icon.ico +0 -0
- benchlab/vu/VU-Server/installer/install.nsi +148 -0
- benchlab/vu/VU-Server/make_version.py +40 -0
- benchlab/vu/VU-Server/pylintrc +628 -0
- benchlab/vu/VU-Server/requirements.txt +9 -0
- benchlab/vu/VU-Server/serial_driver.py +244 -0
- benchlab/vu/VU-Server/server.py +670 -0
- benchlab/vu/VU-Server/server_config.py +255 -0
- benchlab/vu/VU-Server/server_dial_handler.py +334 -0
- benchlab/vu/VU-Server/vu_notifications.py +24 -0
- benchlab/vu/VU-Server/www/android-chrome-192x192.png +0 -0
- benchlab/vu/VU-Server/www/android-chrome-512x512.png +0 -0
- benchlab/vu/VU-Server/www/apple-touch-icon.png +0 -0
- benchlab/vu/VU-Server/www/assets/css/demo.css +291 -0
- benchlab/vu/VU-Server/www/assets/css/demo.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/demo.rtl.css +276 -0
- benchlab/vu/VU-Server/www/assets/css/demo.rtl.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-flags.css +1084 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-flags.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.css +1084 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-flags.rtl.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-payments.css +528 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-payments.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.css +528 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-payments.rtl.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-social.css +10 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-social.min.css +8 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.css +10 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-social.rtl.min.css +8 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-vendors.css +1504 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-vendors.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.css +1504 -0
- benchlab/vu/VU-Server/www/assets/css/tabler-vendors.rtl.min.css +9 -0
- benchlab/vu/VU-Server/www/assets/css/tabler.css +25813 -0
- benchlab/vu/VU-Server/www/assets/css/tabler.min.css +13 -0
- benchlab/vu/VU-Server/www/assets/css/tabler.rtl.css +25777 -0
- benchlab/vu/VU-Server/www/assets/css/tabler.rtl.min.css +13 -0
- benchlab/vu/VU-Server/www/assets/css/theme.min.css +261 -0
- benchlab/vu/VU-Server/www/assets/img/header.png +0 -0
- benchlab/vu/VU-Server/www/assets/img/logo.png +0 -0
- benchlab/vu/VU-Server/www/assets/js/dark-theme.js +35 -0
- benchlab/vu/VU-Server/www/assets/js/dark-theme.min.js +9 -0
- benchlab/vu/VU-Server/www/assets/js/demo.js +132 -0
- benchlab/vu/VU-Server/www/assets/js/demo.min.js +9 -0
- benchlab/vu/VU-Server/www/assets/js/jquery.min.js +2 -0
- benchlab/vu/VU-Server/www/assets/js/tabler.esm.js +7482 -0
- benchlab/vu/VU-Server/www/assets/js/tabler.esm.min.js +15 -0
- benchlab/vu/VU-Server/www/assets/js/tabler.js +7543 -0
- benchlab/vu/VU-Server/www/assets/js/tabler.min.js +15 -0
- benchlab/vu/VU-Server/www/assets/js/vu1_api.js +68 -0
- benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_key_settings.js +167 -0
- benchlab/vu/VU-Server/www/assets/js/vu1_gui_api_keys.js +138 -0
- benchlab/vu/VU-Server/www/assets/js/vu1_gui_dial.js +179 -0
- benchlab/vu/VU-Server/www/assets/js/vu1_gui_home.js +60 -0
- benchlab/vu/VU-Server/www/assets/js/vu1_gui_root.js +125 -0
- benchlab/vu/VU-Server/www/favicon-16x16.png +0 -0
- benchlab/vu/VU-Server/www/favicon-32x32.png +0 -0
- benchlab/vu/VU-Server/www/favicon.ico +0 -0
- benchlab/vu/VU-Server/www/favicon.png +0 -0
- benchlab/vu/VU-Server/www/index.html +47 -0
- benchlab/vu/VU-Server/www/views/api_keys.html +151 -0
- benchlab/vu/VU-Server/www/views/dial.html +286 -0
- benchlab/vu/VU-Server/www/views/footer.html +27 -0
- benchlab/vu/VU-Server/www/views/header.html +86 -0
- benchlab/vu/VU-Server/www/views/key_settings.html +132 -0
- benchlab/vu/VU-Server/www/views/start.html +124 -0
- benchlab/vu/__init__.py +5 -0
- benchlab/vu/assets/Barlow-Bold.ttf +0 -0
- benchlab/vu/assets/bl_dial_144x200.png +0 -0
- benchlab/vu/assets/bl_logo_144x200.png +0 -0
- benchlab/vu/devices.py +166 -0
- benchlab/vu/requirements.txt +24 -0
- benchlab/vu/sensors.py +18 -0
- benchlab/vu/vu_dial.config_template +90 -0
- benchlab/vu/vu_logo_gen.py +97 -0
- benchlab/vu/vu_server.config_template +5 -0
- benchlab/vu/vu_server_config.py +40 -0
- benchlab/vu/vu_server_manager.py +192 -0
- benchlab/vu/vu_tui.py +919 -0
- benchlab/vu/vu_updater.py +540 -0
- benchlab/wigidash/README.md +182 -0
- benchlab/wigidash/__init__.py +5 -0
- benchlab/wigidash/assets/Inter-Bold.ttf +0 -0
- benchlab/wigidash/assets/Inter.ttf +0 -0
- benchlab/wigidash/assets/benchlab.png +0 -0
- benchlab/wigidash/benchlab_fleet.py +150 -0
- benchlab/wigidash/benchlab_graph.py +517 -0
- benchlab/wigidash/benchlab_overview.py +426 -0
- benchlab/wigidash/benchlab_telemetry.py +105 -0
- benchlab/wigidash/benchlab_ui.py +190 -0
- benchlab/wigidash/benchlab_utils.py +182 -0
- benchlab/wigidash/requirements.txt +9 -0
- benchlab/wigidash/wigidash_device.py +541 -0
- benchlab/wigidash/wigidash_manager.py +342 -0
- benchlab/wigidash/wigidash_session.py +355 -0
- benchlab/wigidash/wigidash_usb.py +247 -0
- benchlab/wigidash/wigidash_widget.py +88 -0
- benchlab_pytools-3.0.0.dist-info/METADATA +239 -0
- benchlab_pytools-3.0.0.dist-info/RECORD +184 -0
- benchlab_pytools-3.0.0.dist-info/WHEEL +4 -0
- benchlab_pytools-3.0.0.dist-info/entry_points.txt +2 -0
- benchlab_pytools-3.0.0.dist-info/licenses/LICENSE +21 -0
benchlab/main.py
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
"""BENCHLAB PyTools v2 – Main Launcher.
|
|
2
|
+
|
|
3
|
+
This module implements the command-line entry point for the Benchlab
|
|
4
|
+
telemetry suite. The workflow is split into three steps:
|
|
5
|
+
|
|
6
|
+
1. **Select a data source** – FastAPI, MQTT, direct serial, named pipe,
|
|
7
|
+
or service HTTP.
|
|
8
|
+
2. **Choose consumer tools** – one or many tools that will read from the
|
|
9
|
+
selected source.
|
|
10
|
+
3. **Launch** – start the source (if needed) and then launch the
|
|
11
|
+
selected tools, handling cleanup on exit.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import curses
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
import traceback
|
|
20
|
+
|
|
21
|
+
from .tools import CONSUMER_TOOLS, LAUNCH_PROFILES # noqa: F401
|
|
22
|
+
# CONSUMER_TOOLS re-exported for benchlab.main.CONSUMER_TOOLS consumers
|
|
23
|
+
from .sources import check_and_setup_source, cleanup_all_services
|
|
24
|
+
from .launcher import launch_tools_concurrent
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger("benchlab.launcher")
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from .menu import interactive_loop
|
|
30
|
+
except ImportError as e:
|
|
31
|
+
# prompt_toolkit isn't installed (or failed to import for some other
|
|
32
|
+
# reason) — fall back to the plain numbered-input menu rather than
|
|
33
|
+
# crashing the whole interactive entry point.
|
|
34
|
+
logger.debug(
|
|
35
|
+
f"Falling back to classic menu (prompt_toolkit unavailable: {e})")
|
|
36
|
+
from .menu_classic import interactive_loop
|
|
37
|
+
|
|
38
|
+
# ──────────────────────────────────────────────────────────────
|
|
39
|
+
# Argument Parser
|
|
40
|
+
# ──────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_parser() -> argparse.ArgumentParser:
|
|
44
|
+
from . import __version__
|
|
45
|
+
|
|
46
|
+
parser = argparse.ArgumentParser(
|
|
47
|
+
description="BENCHLAB PyTools v2 - Device Telemetry Suite"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
parser.add_argument("--version", action="version",
|
|
51
|
+
version=f"benchlab-pytools {__version__}")
|
|
52
|
+
parser.add_argument("-config", action="store_true",
|
|
53
|
+
help="Device configuration import/export tool")
|
|
54
|
+
parser.add_argument("-fastapi", action="store_true",
|
|
55
|
+
help="Launch FastAPI telemetry API server")
|
|
56
|
+
parser.add_argument("-graph", action="store_true",
|
|
57
|
+
help="Launch GUI graphing mode")
|
|
58
|
+
parser.add_argument("-hwinfo", action="store_true",
|
|
59
|
+
help="Export sensors to HWiNFO custom sensors")
|
|
60
|
+
parser.add_argument("-i", "--interval", type=float, default=1.0,
|
|
61
|
+
help="Refresh interval in seconds")
|
|
62
|
+
parser.add_argument("-logfleet", action="store_true",
|
|
63
|
+
help="Run CSV logger without TUI")
|
|
64
|
+
parser.add_argument("-mqtt", nargs="?", const="localhost",
|
|
65
|
+
help="MQTT publisher to localhost mosquitto")
|
|
66
|
+
parser.add_argument("-link", action="store_true",
|
|
67
|
+
help="Run cloud MQTT link publisher")
|
|
68
|
+
parser.add_argument(
|
|
69
|
+
"--remote-host",
|
|
70
|
+
default=None,
|
|
71
|
+
dest="remote_host",
|
|
72
|
+
help="Cloud MQTT broker hostname (overrides LINK_REMOTE_HOST)")
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
"--remote-port",
|
|
75
|
+
type=int,
|
|
76
|
+
default=None,
|
|
77
|
+
dest="remote_port",
|
|
78
|
+
help="Cloud MQTT broker port (default: 8883)")
|
|
79
|
+
parser.add_argument(
|
|
80
|
+
"--remote-user",
|
|
81
|
+
default=None,
|
|
82
|
+
dest="remote_user",
|
|
83
|
+
help="Cloud MQTT username (overrides LINK_REMOTE_USER)")
|
|
84
|
+
parser.add_argument(
|
|
85
|
+
"--remote-pass",
|
|
86
|
+
default=None,
|
|
87
|
+
dest="remote_pass",
|
|
88
|
+
help="Cloud MQTT password (overrides LINK_REMOTE_PASS)")
|
|
89
|
+
parser.add_argument("--no-tls", action="store_true", dest="no_tls",
|
|
90
|
+
help="Disable TLS for cloud MQTT connection")
|
|
91
|
+
parser.add_argument(
|
|
92
|
+
"--topic-pattern",
|
|
93
|
+
default=None,
|
|
94
|
+
dest="topic_pattern",
|
|
95
|
+
help=(
|
|
96
|
+
"MQTT topic pattern with {uid} token "
|
|
97
|
+
"(overrides LINK_TOPIC_PATTERN)"))
|
|
98
|
+
parser.add_argument("-tui", action="store_true",
|
|
99
|
+
help="Enable TUI (default)")
|
|
100
|
+
parser.add_argument(
|
|
101
|
+
"--source",
|
|
102
|
+
help=(
|
|
103
|
+
"Data source: direct | fastapi | fastapi_custom | mqtt | "
|
|
104
|
+
"mqtt_custom | named_pipe | service_http"),
|
|
105
|
+
choices=[
|
|
106
|
+
"direct",
|
|
107
|
+
"fastapi",
|
|
108
|
+
"fastapi_custom",
|
|
109
|
+
"mqtt",
|
|
110
|
+
"mqtt_custom",
|
|
111
|
+
"named_pipe",
|
|
112
|
+
"service_http"],
|
|
113
|
+
default=None,
|
|
114
|
+
metavar="SOURCE")
|
|
115
|
+
parser.add_argument(
|
|
116
|
+
"--api-url",
|
|
117
|
+
default="http://127.0.0.1:8000",
|
|
118
|
+
dest="api_url",
|
|
119
|
+
help="FastAPI base URL (default: http://127.0.0.1:8000)")
|
|
120
|
+
parser.add_argument("--api-port", type=int, default=8000,
|
|
121
|
+
dest="api_port",
|
|
122
|
+
help="FastAPI port (default: 8000)")
|
|
123
|
+
parser.add_argument("--mqtt-broker", default="localhost",
|
|
124
|
+
dest="mqtt_broker",
|
|
125
|
+
help="MQTT broker host (default: localhost)")
|
|
126
|
+
parser.add_argument("--mqtt-port", type=int, default=1883,
|
|
127
|
+
dest="mqtt_port",
|
|
128
|
+
help="MQTT broker port (default: 1883)")
|
|
129
|
+
parser.add_argument(
|
|
130
|
+
"--service-url",
|
|
131
|
+
default="http://localhost:8585",
|
|
132
|
+
dest="service_url",
|
|
133
|
+
help=(
|
|
134
|
+
"C# BenchLab service HTTP API URL "
|
|
135
|
+
"(default: http://localhost:8585)"))
|
|
136
|
+
parser.add_argument("-vu", action="store_true",
|
|
137
|
+
help="Launch VU analog dials")
|
|
138
|
+
parser.add_argument("-vuconfig", action="store_true",
|
|
139
|
+
help="Launch VU configuration interface")
|
|
140
|
+
parser.add_argument("-wigidash", action="store_true",
|
|
141
|
+
help="Connect to WigiDash")
|
|
142
|
+
parser.add_argument("--profile",
|
|
143
|
+
help="Launch predefined multi-tool profile",
|
|
144
|
+
default=None)
|
|
145
|
+
|
|
146
|
+
return parser
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# ──────────────────────────────────────────────────────────────
|
|
150
|
+
# Source Setup from CLI Args
|
|
151
|
+
# ──────────────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
def _setup_source_from_args(args) -> bool:
|
|
154
|
+
"""Resolve and start the data source requested via --source (or env
|
|
155
|
+
fallback).
|
|
156
|
+
|
|
157
|
+
Returns True if the source is ready, False on failure.
|
|
158
|
+
"""
|
|
159
|
+
source = args.source or os.environ.get("BENCHLAB_DATA_SOURCE", "direct")
|
|
160
|
+
logger.info(f"Setting up data source: {source}")
|
|
161
|
+
|
|
162
|
+
if source == "fastapi":
|
|
163
|
+
port = getattr(
|
|
164
|
+
args,
|
|
165
|
+
"api_port",
|
|
166
|
+
None) or int(
|
|
167
|
+
os.environ.get(
|
|
168
|
+
"API_PORT",
|
|
169
|
+
"8000"))
|
|
170
|
+
os.environ["API_PORT"] = str(port)
|
|
171
|
+
os.environ["BENCHLAB_API_URL"] = getattr(
|
|
172
|
+
args, "api_url", f"http://127.0.0.1:{port}")
|
|
173
|
+
ready = check_and_setup_source("fastapi", port=port)
|
|
174
|
+
|
|
175
|
+
elif source == "fastapi_custom":
|
|
176
|
+
api_url = getattr(
|
|
177
|
+
args,
|
|
178
|
+
"api_url",
|
|
179
|
+
os.environ.get(
|
|
180
|
+
"BENCHLAB_API_URL",
|
|
181
|
+
"http://127.0.0.1:8000"))
|
|
182
|
+
os.environ["BENCHLAB_API_URL"] = api_url
|
|
183
|
+
ready = check_and_setup_source("fastapi_custom", base_url=api_url)
|
|
184
|
+
|
|
185
|
+
elif source == "mqtt":
|
|
186
|
+
broker = getattr(
|
|
187
|
+
args,
|
|
188
|
+
"mqtt_broker",
|
|
189
|
+
None) or os.environ.get(
|
|
190
|
+
"MQTT_BROKER",
|
|
191
|
+
"localhost")
|
|
192
|
+
mqtt_port = getattr(
|
|
193
|
+
args,
|
|
194
|
+
"mqtt_port",
|
|
195
|
+
None) or int(
|
|
196
|
+
os.environ.get(
|
|
197
|
+
"MQTT_PORT",
|
|
198
|
+
"1883"))
|
|
199
|
+
os.environ["MQTT_BROKER"] = broker
|
|
200
|
+
os.environ["MQTT_PORT"] = str(mqtt_port)
|
|
201
|
+
ready = check_and_setup_source(
|
|
202
|
+
"mqtt", broker=broker, mqtt_port=mqtt_port)
|
|
203
|
+
|
|
204
|
+
elif source == "mqtt_custom":
|
|
205
|
+
broker = getattr(
|
|
206
|
+
args,
|
|
207
|
+
"mqtt_broker",
|
|
208
|
+
None) or os.environ.get(
|
|
209
|
+
"MQTT_BROKER",
|
|
210
|
+
"localhost")
|
|
211
|
+
mqtt_port = getattr(
|
|
212
|
+
args,
|
|
213
|
+
"mqtt_port",
|
|
214
|
+
None) or int(
|
|
215
|
+
os.environ.get(
|
|
216
|
+
"MQTT_PORT",
|
|
217
|
+
"1883"))
|
|
218
|
+
os.environ["MQTT_BROKER"] = broker
|
|
219
|
+
os.environ["MQTT_PORT"] = str(mqtt_port)
|
|
220
|
+
ready = check_and_setup_source(
|
|
221
|
+
"mqtt_custom", broker=broker, mqtt_port=mqtt_port)
|
|
222
|
+
|
|
223
|
+
elif source == "named_pipe":
|
|
224
|
+
ready = check_and_setup_source("named_pipe")
|
|
225
|
+
|
|
226
|
+
elif source == "service_http":
|
|
227
|
+
service_url = getattr(args, "service_url", None) or os.environ.get(
|
|
228
|
+
"BENCHLAB_SERVICE_URL", "http://localhost:8585"
|
|
229
|
+
)
|
|
230
|
+
os.environ["BENCHLAB_SERVICE_URL"] = service_url
|
|
231
|
+
# Parse host/port from URL for the check
|
|
232
|
+
import urllib.parse
|
|
233
|
+
parsed = urllib.parse.urlparse(service_url)
|
|
234
|
+
ready = check_and_setup_source(
|
|
235
|
+
"service_http",
|
|
236
|
+
host=parsed.hostname or "localhost",
|
|
237
|
+
port=parsed.port or 8585,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
else:
|
|
241
|
+
ready = check_and_setup_source("direct")
|
|
242
|
+
|
|
243
|
+
if ready:
|
|
244
|
+
os.environ["BENCHLAB_DATA_SOURCE"] = source
|
|
245
|
+
else:
|
|
246
|
+
logger.error(f"Could not set up '{source}' data source.")
|
|
247
|
+
return ready
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _export_link_env(args) -> None:
|
|
251
|
+
"""Mirror --remote-*/--no-tls/--topic-pattern CLI flags into env vars.
|
|
252
|
+
|
|
253
|
+
link_main.py's _resolve_config already falls back to these env vars
|
|
254
|
+
(REMOTE_MQTT_HOST etc.) when no args object provides a value, so
|
|
255
|
+
exporting here is what lets a spawned/multi-tool `link` process
|
|
256
|
+
(launched via launcher.py's _spawn_tool_in_terminal, which parses
|
|
257
|
+
fresh argv with none of these flags) still pick up the config the
|
|
258
|
+
user passed on the parent process's command line.
|
|
259
|
+
"""
|
|
260
|
+
mapping = {
|
|
261
|
+
"remote_host": "REMOTE_MQTT_HOST",
|
|
262
|
+
"remote_port": "REMOTE_MQTT_PORT",
|
|
263
|
+
"remote_user": "REMOTE_MQTT_USER",
|
|
264
|
+
"remote_pass": "REMOTE_MQTT_PASS",
|
|
265
|
+
"topic_pattern": "LINK_TOPIC_PATTERN",
|
|
266
|
+
}
|
|
267
|
+
for attr, env_key in mapping.items():
|
|
268
|
+
val = getattr(args, attr, None)
|
|
269
|
+
if val is not None:
|
|
270
|
+
os.environ[env_key] = str(val)
|
|
271
|
+
if getattr(args, "no_tls", False):
|
|
272
|
+
os.environ["REMOTE_MQTT_TLS"] = "false"
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# ──────────────────────────────────────────────────────────────
|
|
276
|
+
# Per-tool CLI Dispatch Helpers
|
|
277
|
+
# ──────────────────────────────────────────────────────────────
|
|
278
|
+
|
|
279
|
+
def _run_with_source(
|
|
280
|
+
args,
|
|
281
|
+
import_path: str,
|
|
282
|
+
func_name: str,
|
|
283
|
+
call_fn,
|
|
284
|
+
tool_label: str) -> None:
|
|
285
|
+
"""Set up source, call call_fn, then clean up."""
|
|
286
|
+
from .tools import ensure_tool_dependencies
|
|
287
|
+
tool_id = next((tid for tid, t in CONSUMER_TOOLS.items()
|
|
288
|
+
if t["module"] == import_path), None)
|
|
289
|
+
if tool_id:
|
|
290
|
+
try:
|
|
291
|
+
ensure_tool_dependencies(tool_id)
|
|
292
|
+
except Exception as e:
|
|
293
|
+
logger.warning(
|
|
294
|
+
f"Failed to install dependencies for {tool_label}: {e}")
|
|
295
|
+
|
|
296
|
+
if not _setup_source_from_args(args):
|
|
297
|
+
return
|
|
298
|
+
try:
|
|
299
|
+
mod = __import__(import_path, fromlist=[func_name])
|
|
300
|
+
call_fn(getattr(mod, func_name))
|
|
301
|
+
except ModuleNotFoundError:
|
|
302
|
+
traceback.print_exc()
|
|
303
|
+
print(f"{tool_label} module not available in this build.")
|
|
304
|
+
finally:
|
|
305
|
+
cleanup_all_services()
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# ──────────────────────────────────────────────────────────────
|
|
309
|
+
# CLI Entry Point
|
|
310
|
+
# ──────────────────────────────────────────────────────────────
|
|
311
|
+
|
|
312
|
+
def launch_mode() -> None:
|
|
313
|
+
"""Parse CLI arguments and dispatch to the appropriate mode."""
|
|
314
|
+
parser = get_parser()
|
|
315
|
+
|
|
316
|
+
# Check if -config is in args - if so, use parse_known_args to allow
|
|
317
|
+
# config tool args through
|
|
318
|
+
if '-config' in sys.argv:
|
|
319
|
+
args, unknown = parser.parse_known_args()
|
|
320
|
+
else:
|
|
321
|
+
args = parser.parse_args()
|
|
322
|
+
|
|
323
|
+
no_flags = not any([args.config,
|
|
324
|
+
args.fastapi,
|
|
325
|
+
args.graph,
|
|
326
|
+
args.hwinfo,
|
|
327
|
+
args.link,
|
|
328
|
+
args.logfleet,
|
|
329
|
+
args.mqtt,
|
|
330
|
+
args.tui,
|
|
331
|
+
args.vu,
|
|
332
|
+
args.vuconfig,
|
|
333
|
+
args.wigidash,
|
|
334
|
+
args.profile,
|
|
335
|
+
])
|
|
336
|
+
if no_flags:
|
|
337
|
+
interactive_loop()
|
|
338
|
+
return
|
|
339
|
+
|
|
340
|
+
# ── Profile ──────────────────────────────────────────────
|
|
341
|
+
if args.profile:
|
|
342
|
+
profile = LAUNCH_PROFILES.get(args.profile)
|
|
343
|
+
if not profile:
|
|
344
|
+
print(f"Unknown profile: {args.profile}")
|
|
345
|
+
return
|
|
346
|
+
print(f"Launching profile: {args.profile}")
|
|
347
|
+
|
|
348
|
+
from .tools import ensure_profile_dependencies
|
|
349
|
+
ensure_profile_dependencies(args.profile)
|
|
350
|
+
|
|
351
|
+
profile_args = argparse.Namespace(
|
|
352
|
+
source=profile.get("source", "direct"),
|
|
353
|
+
api_url=getattr(args, "api_url", "http://127.0.0.1:8000"),
|
|
354
|
+
api_port=getattr(args, "api_port", 8000),
|
|
355
|
+
mqtt_broker=getattr(args, "mqtt_broker", "localhost"),
|
|
356
|
+
mqtt_port=getattr(args, "mqtt_port", 1883),
|
|
357
|
+
service_url=getattr(
|
|
358
|
+
args, "service_url", "http://localhost:8585"),
|
|
359
|
+
)
|
|
360
|
+
if not _setup_source_from_args(profile_args):
|
|
361
|
+
print("Failed to initialize data source")
|
|
362
|
+
return
|
|
363
|
+
os.environ["BENCHLAB_DATA_SOURCE"] = profile.get("source", "direct")
|
|
364
|
+
launch_tools_concurrent(profile["tools"])
|
|
365
|
+
return
|
|
366
|
+
|
|
367
|
+
# ── Individual tool flags ─────────────────────────────────
|
|
368
|
+
if args.config:
|
|
369
|
+
# Config tool handles its own argument parsing
|
|
370
|
+
# Remove -config from sys.argv so config tool can parse remaining args
|
|
371
|
+
try:
|
|
372
|
+
sys.argv.remove('-config')
|
|
373
|
+
from benchlab.config.config_tool import main as config_main
|
|
374
|
+
sys.exit(config_main())
|
|
375
|
+
except ModuleNotFoundError:
|
|
376
|
+
print("Config tool module not available in this build.")
|
|
377
|
+
|
|
378
|
+
elif args.fastapi:
|
|
379
|
+
try:
|
|
380
|
+
from benchlab.restapi.telemetry_api import run_server
|
|
381
|
+
run_server()
|
|
382
|
+
except ModuleNotFoundError:
|
|
383
|
+
print("FastAPI / Uvicorn not available in this build.")
|
|
384
|
+
|
|
385
|
+
elif args.mqtt:
|
|
386
|
+
try:
|
|
387
|
+
from benchlab.mqtt.mqtt_publisher import run_mqtt_mode
|
|
388
|
+
broker = args.mqtt if args.mqtt else "localhost"
|
|
389
|
+
os.environ.setdefault("MQTT_BROKER", broker)
|
|
390
|
+
run_mqtt_mode(broker)
|
|
391
|
+
except ModuleNotFoundError:
|
|
392
|
+
print("MQTT module not available in this build.")
|
|
393
|
+
|
|
394
|
+
elif args.link:
|
|
395
|
+
_export_link_env(args)
|
|
396
|
+
_run_with_source(args,
|
|
397
|
+
"benchlab.link.link_main", "run_link",
|
|
398
|
+
lambda fn: fn(args), "Link")
|
|
399
|
+
|
|
400
|
+
elif args.graph:
|
|
401
|
+
_run_with_source(args,
|
|
402
|
+
"benchlab.graph.runner", "run_graph_mode",
|
|
403
|
+
lambda fn: fn(args), "Graph")
|
|
404
|
+
|
|
405
|
+
elif args.hwinfo:
|
|
406
|
+
_run_with_source(
|
|
407
|
+
args,
|
|
408
|
+
"benchlab.hwinfo.hwinfo_export",
|
|
409
|
+
"export_all_devices",
|
|
410
|
+
lambda fn: fn(
|
|
411
|
+
update_interval=args.interval),
|
|
412
|
+
"HWiNFO export")
|
|
413
|
+
|
|
414
|
+
elif args.logfleet:
|
|
415
|
+
_run_with_source(
|
|
416
|
+
args,
|
|
417
|
+
"benchlab.csv_log.csv_logger_enhanced",
|
|
418
|
+
"run_enhanced_csv_logger",
|
|
419
|
+
lambda fn: fn(args),
|
|
420
|
+
"Enhanced CSV logger")
|
|
421
|
+
|
|
422
|
+
elif args.vu:
|
|
423
|
+
_run_with_source(args,
|
|
424
|
+
"benchlab.vu.vu_updater", "run_updater",
|
|
425
|
+
lambda fn: fn(args), "VU")
|
|
426
|
+
|
|
427
|
+
elif args.vuconfig:
|
|
428
|
+
_run_with_source(args,
|
|
429
|
+
"benchlab.vu.vu_tui", "launch_vu_config",
|
|
430
|
+
lambda fn: fn(args), "VU configuration")
|
|
431
|
+
|
|
432
|
+
elif args.wigidash:
|
|
433
|
+
_run_with_source(args,
|
|
434
|
+
"benchlab.wigidash.wigidash_manager", "main",
|
|
435
|
+
lambda fn: fn(args), "WigiDash")
|
|
436
|
+
|
|
437
|
+
elif args.tui:
|
|
438
|
+
if not _setup_source_from_args(args):
|
|
439
|
+
return
|
|
440
|
+
try:
|
|
441
|
+
from benchlab.tui.tui_main import tui_main
|
|
442
|
+
curses.wrapper(tui_main, None, args)
|
|
443
|
+
except KeyboardInterrupt:
|
|
444
|
+
pass
|
|
445
|
+
except ModuleNotFoundError:
|
|
446
|
+
logger.error("TUI module not available in this build.")
|
|
447
|
+
finally:
|
|
448
|
+
cleanup_all_services()
|
|
449
|
+
|
|
450
|
+
else:
|
|
451
|
+
logger.info(
|
|
452
|
+
"No specific mode flags provided; launching interactive loop.")
|
|
453
|
+
interactive_loop()
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def main() -> None:
|
|
457
|
+
"""Entry point."""
|
|
458
|
+
launch_mode()
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
if __name__ == "__main__":
|
|
462
|
+
main()
|