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,224 @@
1
+ """
2
+ Statistics Module for TUI and other tools
3
+
4
+ Provides thread-safe channel statistics tracking with min/max/average
5
+ calculations that can be used by any tool consuming telemetry data.
6
+ """
7
+
8
+ import threading
9
+ from collections import defaultdict
10
+ from typing import Dict, Optional, Tuple
11
+
12
+
13
+ def _make_stat() -> Dict[str, float]:
14
+ """Create a new statistics dictionary."""
15
+ return {'min': float('inf'), 'max': float('-inf'), 'sum': 0.0, 'count': 0}
16
+
17
+
18
+ class ChannelStats:
19
+ """Thread-safe per-device, per-channel running statistics.
20
+
21
+ Tracks minimum, maximum, and average values for numeric telemetry channels
22
+ across multiple devices. Can be used by TUI, dashboard, or any other tool
23
+ that needs statistical analysis of telemetry data.
24
+ """
25
+
26
+ def __init__(self):
27
+ """Initialize statistics tracker."""
28
+ self._lock = threading.Lock()
29
+ # device -> channel -> stat dict
30
+ self._data: Dict[str, Dict[str, Dict[str, float]]
31
+ ] = defaultdict(lambda: defaultdict(_make_stat))
32
+
33
+ def update(self, device: str, channel: str, value: float) -> None:
34
+ """Update statistics for a device channel.
35
+
36
+ Args:
37
+ device: Device identifier (UID or port)
38
+ channel: Channel name (e.g., 'SYS_Power', 'Chip_Temp')
39
+ value: Numeric value to add to statistics
40
+ """
41
+ if value is None or not isinstance(value, (int, float)):
42
+ return
43
+
44
+ with self._lock:
45
+ s = self._data[device][channel]
46
+ if value < s['min']:
47
+ s['min'] = value
48
+ if value > s['max']:
49
+ s['max'] = value
50
+ s['sum'] += value
51
+ s['count'] += 1
52
+
53
+ def get(self,
54
+ device: str,
55
+ channel: str) -> Tuple[Optional[float],
56
+ Optional[float],
57
+ Optional[float]]:
58
+ """Get statistics for a device channel.
59
+
60
+ Args:
61
+ device: Device identifier
62
+ channel: Channel name
63
+
64
+ Returns:
65
+ Tuple of (min, max, avg) or (None, None, None) if no data
66
+ """
67
+ with self._lock:
68
+ s = self._data[device][channel]
69
+ if s['count'] == 0:
70
+ return None, None, None
71
+ return s['min'], s['max'], s['sum'] / s['count']
72
+
73
+ def get_all(self,
74
+ device: str) -> Dict[str,
75
+ Tuple[Optional[float],
76
+ Optional[float],
77
+ Optional[float]]]:
78
+ """Get statistics for all channels of a device.
79
+
80
+ Args:
81
+ device: Device identifier
82
+
83
+ Returns:
84
+ Dictionary mapping channel names to (min, max, avg) tuples
85
+ """
86
+ with self._lock:
87
+ result = {}
88
+ for channel in self._data[device]:
89
+ s = self._data[device][channel]
90
+ if s['count'] == 0:
91
+ result[channel] = (None, None, None)
92
+ else:
93
+ result[channel] = (
94
+ s['min'], s['max'], s['sum'] / s['count'])
95
+ return result
96
+
97
+ def reset(self, device: Optional[str] = None) -> None:
98
+ """Reset statistics for a device or all devices.
99
+
100
+ Args:
101
+ device: Device identifier to reset, or None to reset all devices
102
+ """
103
+ with self._lock:
104
+ if device is None:
105
+ # Reset all devices
106
+ self._data.clear()
107
+ else:
108
+ # Reset specific device
109
+ if device in self._data:
110
+ self._data[device].clear()
111
+
112
+ def get_devices(self) -> list[str]:
113
+ """Get list of all devices with statistics.
114
+
115
+ Returns:
116
+ List of device identifiers with recorded statistics
117
+ """
118
+ with self._lock:
119
+ return list(self._data.keys())
120
+
121
+ def get_channels(self, device: str) -> list[str]:
122
+ """Get list of all channels for a device.
123
+
124
+ Args:
125
+ device: Device identifier
126
+
127
+ Returns:
128
+ List of channel names with recorded statistics for the device
129
+ """
130
+ with self._lock:
131
+ if device in self._data:
132
+ return list(self._data[device].keys())
133
+ return []
134
+
135
+ def has_data(self, device: str, channel: Optional[str] = None) -> bool:
136
+ """Check if statistics data exists.
137
+
138
+ Args:
139
+ device: Device identifier
140
+ channel: Optional channel name to check specifically
141
+
142
+ Returns:
143
+ True if statistics data exists
144
+ """
145
+ with self._lock:
146
+ if device not in self._data:
147
+ return False
148
+ if channel is None:
149
+ return bool(self._data[device])
150
+ return (channel in self._data[device]
151
+ and self._data[device][channel]['count'] > 0)
152
+
153
+
154
+ class StatsFormatter:
155
+ """Helper class for formatting statistics for display."""
156
+
157
+ @staticmethod
158
+ def format_stat_string(min_val: Optional[float], max_val: Optional[float],
159
+ avg_val: Optional[float], decimals: int = 1,
160
+ unit: str = "", width: int = 10) -> str:
161
+ """Format statistics as a display string.
162
+
163
+ Args:
164
+ min_val: Minimum value
165
+ max_val: Maximum value
166
+ avg_val: Average value
167
+ decimals: Number of decimal places
168
+ unit: Unit string (e.g., 'W', '°C', 'V')
169
+ width: Fixed width for each stat column
170
+
171
+ Returns:
172
+ Formatted string like "↓10.2W ↑15.8W ~13.1W"
173
+ """
174
+ if min_val is None or max_val is None or avg_val is None:
175
+ return ""
176
+
177
+ lo = f"↓{min_val:.{decimals}f}{unit}"
178
+ hi = f"↑{max_val:.{decimals}f}{unit}"
179
+ av = f"~{avg_val:.{decimals}f}{unit}"
180
+
181
+ return f" {lo:<{width}} {hi:<{width}} {av:<{width}}"
182
+
183
+ @staticmethod
184
+ def format_compact_range(
185
+ min_val: Optional[float],
186
+ max_val: Optional[float],
187
+ avg_val: Optional[float],
188
+ decimals: int = 1,
189
+ unit: str = "") -> str:
190
+ """Format statistics as a compact range string.
191
+
192
+ Args:
193
+ min_val: Minimum value
194
+ max_val: Maximum value
195
+ avg_val: Average value
196
+ decimals: Number of decimal places
197
+ unit: Unit string
198
+
199
+ Returns:
200
+ Formatted string like "10.2-15.8 ~13.1W"
201
+ """
202
+ if min_val is None or max_val is None or avg_val is None:
203
+ return ""
204
+
205
+ return f"{
206
+ min_val:.{decimals}f}-{
207
+ max_val:.{decimals}f} ~{
208
+ avg_val:.{decimals}f}{unit}"
209
+
210
+
211
+ # Convenience function for creating a stats callback
212
+ def create_stats_callback(stats: ChannelStats):
213
+ """Create a callback function for DataSourceManager.
214
+
215
+ Args:
216
+ stats: ChannelStats instance to update
217
+
218
+ Returns:
219
+ Callback function that can be passed to DataSourceManager
220
+ """
221
+ def callback(device_uid: str, channel: str, value: float):
222
+ stats.update(device_uid, channel, value)
223
+
224
+ return callback
@@ -0,0 +1,206 @@
1
+ # Enhanced BENCHLAB CSV Fleet Logger
2
+
3
+ ## Overview
4
+
5
+ The Enhanced CSV Fleet Logger provides robust, lightweight telemetry logging for BENCHLAB devices with improved error handling, performance optimization, and cross-platform compatibility.
6
+
7
+ ### Key Improvements
8
+
9
+ - **Configuration-driven**: Support for config files and environment variables
10
+ - **Enhanced error handling**: Exponential backoff retry logic and auto-reconnection
11
+ - **Performance optimized**: Buffered writes and async file I/O
12
+ - **Cross-platform**: Optimized for both Windows and Linux environments
13
+ - **Multiple formats**: Support for CSV and JSON output formats
14
+ - **Silent operation**: Headless mode for automated deployments
15
+ - **Connection monitoring**: Automatic reconnection for dropped devices
16
+
17
+ ---
18
+
19
+ ## Features
20
+
21
+ | Feature | Description |
22
+ |---------|-------------|
23
+ | **Configurable operation** | File-based and environment variable configuration |
24
+ | **Smart device discovery** | Enhanced error handling during device detection |
25
+ | **Buffered writes** | Configurable buffer size for optimal disk I/O |
26
+ | **Auto-reconnection** | Automatic reconnection for dropped serial connections |
27
+ | **Multiple output formats** | CSV and JSON format support |
28
+ | **Silent mode** | Headless operation for automated scripts |
29
+ | **Cross-platform** | Optimized for Windows, Linux, and embedded systems |
30
+ | **Memory efficient** | Circular buffers and automatic cleanup |
31
+ | **Detailed logging** | Configurable log levels and structured output |
32
+
33
+ ---
34
+
35
+ ## Installation
36
+
37
+ Install the tool's dependencies (from `benchlab/csv_log/requirements.txt`):
38
+
39
+ ```bash
40
+ pip install -r benchlab/csv_log/requirements.txt
41
+ ```
42
+
43
+ Key dependencies: `pyserial`, `benchlab-pycore`.
44
+
45
+ ---
46
+
47
+ ## Configuration
48
+
49
+ ### Configuration File
50
+
51
+ Create a `csv_logger.config` file in your working directory. Only the settings below are actually read by `LoggerConfig`/`load_config()`; other keys are ignored:
52
+
53
+ ```ini
54
+ [logger]
55
+ interval = 1.0 # Logging interval in seconds
56
+ output_dir = logs # Output directory
57
+ buffer_size = 100 # Rows buffered before a batched write
58
+ format = csv # Output format: csv or json (currently informational; batcher writes CSV)
59
+ silent_mode = false # Silent operation (also auto-selects devices)
60
+ auto_select = false # Auto-select all discovered devices, skip the prompt
61
+ ```
62
+
63
+ ### Environment Variables
64
+
65
+ Override select settings with environment variables (read in `load_config()`):
66
+
67
+ ```bash
68
+ export CSV_LOG_INTERVAL=0.5
69
+ export CSV_LOG_OUTPUT_DIR=/var/log/benchlab
70
+ export CSV_LOG_BUFFER_SIZE=200
71
+ export CSV_LOG_SILENT=true
72
+ export CSV_LOG_AUTO_SELECT=true
73
+ ```
74
+
75
+ `BENCHLAB_AUTO_SELECT=true` is also honored by `run_enhanced_csv_logger()` (used when launched via `python -m benchlab -logfleet`) to force auto-selection.
76
+
77
+ ---
78
+
79
+ ## Usage
80
+
81
+ ### Command Line Interface
82
+
83
+ Via the main BENCHLAB launcher (recommended):
84
+
85
+ ```bash
86
+ # Basic usage with default settings (direct/serial source, 1s interval)
87
+ python -m benchlab -logfleet
88
+
89
+ # Custom interval
90
+ python -m benchlab -logfleet -i 0.5
91
+
92
+ # Choose a data source (direct | fastapi | fastapi_custom | mqtt | mqtt_custom | named_pipe | service_http)
93
+ python -m benchlab -logfleet --source fastapi --api-url http://127.0.0.1:8000
94
+ python -m benchlab -logfleet --source mqtt --mqtt-broker localhost --mqtt-port 1883
95
+ ```
96
+
97
+ `-i`/`--interval`, `--source`, `--api-url`, `--api-port`, `--mqtt-broker`, and `--mqtt-port` are the standard `benchlab/main.py` CLI flags; the tool itself has no `-logfleet`-specific flags beyond what `main.py` exposes.
98
+
99
+ Running the module standalone (bypasses the main launcher, uses its own small argparse CLI limited to `direct`/`fastapi`/`mqtt` sources):
100
+
101
+ ```bash
102
+ python -m benchlab.csv_log.csv_logger_enhanced -i 0.5 -c my_config.config
103
+ python -m benchlab.csv_log.csv_logger_enhanced --silent --auto-select
104
+ ```
105
+
106
+ ### Programmatic Usage
107
+
108
+ ```python
109
+ from benchlab.csv_log.csv_logger_enhanced import EnhancedCSVLogger, LoggerConfig
110
+ import types
111
+
112
+ # Create configuration
113
+ config = LoggerConfig(
114
+ interval=0.5,
115
+ output_dir="custom_logs",
116
+ buffer_size=50,
117
+ silent_mode=True,
118
+ auto_select=True,
119
+ )
120
+
121
+ # args mirrors the standard benchlab CLI namespace (source/api_url/mqtt_broker/mqtt_port)
122
+ args = types.SimpleNamespace(source="direct", interval=0.5, api_url="http://127.0.0.1:8000",
123
+ mqtt_broker="localhost", mqtt_port=1883)
124
+
125
+ # Create and run logger (use as a context manager so stop_logging() always runs)
126
+ with EnhancedCSVLogger(config, args=args) as logger:
127
+ logger.start_logging()
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Output Format
133
+
134
+ Rows are written as CSV via the batching logger (`benchlab/csv_log/message_batcher.py`):
135
+
136
+ ```
137
+ Timestamp,uid,SYS_Power,CPU_Power,GPU_Power,Temp1,Temp2,...
138
+ 2025-10-06T10:15:01.123456,BL-1234,120,50,30,65,70,...
139
+ 2025-10-06T10:15:02.123456,BL-1234,118,49,31,65,71,...
140
+ ```
141
+
142
+ The `format` config key is accepted by `LoggerConfig` but the batcher currently always writes CSV; JSON output is not implemented.
143
+
144
+ ---
145
+
146
+ ## Behavior Notes
147
+
148
+ - **Buffered writes**: rows are batched (default `buffer_size = 100`) and flushed via `BatchingLogger`, with a background flush every 5 seconds and an explicit flush each polling cycle.
149
+ - **Retry on connect**: initial connection to the data source uses `SmartRetryManager` (3 attempts, exponential backoff) — see `benchlab/csv_log/smart_retry.py`.
150
+ - **Silent/auto-select**: `silent_mode` or `auto_select` (or `BENCHLAB_AUTO_SELECT=true`) skip the interactive device-selection prompt and log at `WARNING` level instead of `INFO`.
151
+ - **Graceful shutdown**: Ctrl+C stops the polling loop, flushes the batcher, and disconnects the data source (`EnhancedCSVLogger.stop_logging()`).
152
+
153
+ ---
154
+
155
+ ## Troubleshooting
156
+
157
+ ### Device Not Detected
158
+ ```bash
159
+ python -c "from benchlab_pycore.core import get_benchlab_ports; print(get_benchlab_ports())"
160
+ ```
161
+
162
+ ### Permission Errors (Linux)
163
+ ```bash
164
+ # Add user to dialout group for serial access
165
+ sudo usermod -a -G dialout $USER
166
+ # Log out and back in, or restart
167
+ ```
168
+
169
+ ### No devices selected / logging exits immediately
170
+ Ensure the configured `--source` (direct/fastapi/mqtt/etc.) is actually running and reachable — the logger connects via `DataSourceManager`, and a failed connection or empty device discovery causes it to log an error and stop.
171
+
172
+ ---
173
+
174
+ ## API Reference
175
+
176
+ ### EnhancedCSVLogger Class
177
+
178
+ ```python
179
+ class EnhancedCSVLogger:
180
+ def __init__(self, config: LoggerConfig, args=None)
181
+ def start_logging(self)
182
+ def stop_logging(self)
183
+ def discover_devices(self) -> List[DeviceConfig]
184
+ def select_devices(self, devices: List[DeviceConfig]) -> List[DeviceConfig]
185
+ def create_batcher(self)
186
+ def log_device_data(self, uid: str) -> bool
187
+ ```
188
+
189
+ ### LoggerConfig Dataclass
190
+
191
+ ```python
192
+ @dataclass
193
+ class LoggerConfig:
194
+ interval: float = 1.0
195
+ output_dir: str = "logs"
196
+ buffer_size: int = 100
197
+ format: str = "csv" # accepted, but only CSV output is implemented
198
+ silent_mode: bool = False
199
+ auto_select: bool = False
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Support
205
+
206
+ For issues, check the Troubleshooting section above or report bugs via GitHub issues.
@@ -0,0 +1,5 @@
1
+ """
2
+ BENCHLAB Telemetry Package
3
+ """
4
+
5
+ __version__ = "0.9.0"