70 """Get default board IP for a sensor type from config, with hardcoded fallbacks."""
72 "PT":
"192.168.2.101",
73 "LC":
"192.168.2.102",
74 "TC":
"192.168.2.103",
75 "RTD":
"192.168.2.104",
78 b = get_board_by_type(sensor_type)
80 return b.get(
"ip", fallbacks.get(sensor_type,
""))
81 return fallbacks.get(sensor_type,
"")
163 """Check system health before startup"""
164 logger.info(
"🔍 Performing system health checks...")
171 if sys.version_info >= (3, 8):
172 logger.info(
"✅ Python version OK: " + sys.version.split()[0])
175 logger.error(f
"❌ Python version {sys.version.split()[0]} < 3.8")
179 required_modules = [
"numpy"]
180 optional_modules = [
"PyQt6",
"pyqtgraph",
"serial",
"scipy"]
181 missing_required = []
182 missing_optional = []
184 for module
in required_modules:
188 missing_required.append(module)
190 for module
in optional_modules:
194 missing_optional.append(module)
196 if not missing_required:
197 logger.info(
"✅ All required modules available")
200 f
"⚠️ Optional modules missing: {', '.join(missing_optional)}"
204 logger.error(f
"❌ Missing required modules: {', '.join(missing_required)}")
208 calib_dir = CALIBRATION_DIR /
"calibrations"
209 calib_dir.mkdir(parents=
True, exist_ok=
True)
210 test_file = calib_dir /
"._write_test"
212 test_file.write_text(
"test")
214 logger.info(
"✅ Calibration directory write permissions OK")
217 logger.error(f
"❌ No write permissions in calibration directory: {calib_dir}")
224 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
226 logger.info(
"✅ UDP socket creation OK")
228 except Exception
as e:
229 logger.error(f
"❌ UDP socket test failed: {e}")
231 success_rate = checks_passed / checks_total
if checks_total > 0
else 0
232 logger.info(f
"\n{'='*80}")
234 f
"Health check: {checks_passed}/{checks_total} passed ({success_rate*100:.0f}%)"
236 logger.info(f
"{'='*80}\n")
238 return checks_passed >= checks_total - 1
266 """Interactive tool selection."""
269 tools_list = list(TOOL_REGISTRY.keys())
270 print(
"\nEnter tool number or name:")
271 for i, name
in enumerate(tools_list, 1):
272 info = TOOL_REGISTRY[name]
273 print(f
" {i:>2d}. {name} ({info['sensor_type']})")
276 choice = input(
"\nSelect tool [1-{}]: ".
format(len(tools_list))).strip()
281 idx = int(choice) - 1
282 if 0 <= idx < len(tools_list):
283 return tools_list[idx]
287 if choice
in TOOL_REGISTRY:
289 print(f
" ⚠️ Invalid choice: {choice}")
293 """Build subprocess command for the selected tool."""
294 info = TOOL_REGISTRY[tool_name]
295 script_path = CALIBRATION_DIR / info[
"script"]
296 cmd = [sys.executable, str(script_path.resolve())]
299 if info.get(
"extra_args"):
300 cmd.extend(info[
"extra_args"])
303 if not info.get(
"gui"):
305 is_orchestrator =
"orchestrator" in info.get(
"script",
"")
307 if args.udp_port
and not is_orchestrator:
308 cmd.extend([
"--port", str(args.udp_port)])
309 if is_orchestrator
and args.udp_port:
310 cmd.extend([
"--port", str(args.udp_port)])
312 if not is_orchestrator:
314 cmd.extend([
"--board-ip", args.board_ip])
318 cmd.extend([
"--board-ip", default_ip])
320 if args.ref_all
is not None and not is_orchestrator:
321 cmd.extend([
"--ref-all", str(args.ref_all)])
322 if args.collect_time
and not is_orchestrator:
323 cmd.extend([
"--collect-time", str(args.collect_time)])
324 if args.interactive_cal
and not is_orchestrator:
325 cmd.append(
"--interactive")
328 if args.sensor
and not is_orchestrator:
329 for s
in args.sensor:
330 cmd.extend([
"--sensor", str(s)])
331 if args.ref_value
and not is_orchestrator:
332 flag =
"--force" if info[
"sensor_type"] ==
"LC" else "--temp"
333 for v
in args.ref_value:
334 cmd.extend([flag, str(v)])
337 if is_orchestrator
and info[
"sensor_type"] !=
"ALL":
338 cmd.extend([
"--sensors", info[
"sensor_type"]])
344 parser = argparse.ArgumentParser(
345 description=
"Mission-Critical Calibration System Startup",
346 formatter_class=argparse.RawDescriptionHelpFormatter,
350 python3 start_calibration_system.py
352 # Launch autonomous PT calibration interactively
353 python3 start_calibration_system.py --tool autonomous_pt_calibration -i
355 # Collect TC calibration at 100°C for 15 seconds
356 python3 start_calibration_system.py --tool autonomous_tc_calibration --ref-all 100.0 --collect-time 15
359 python3 start_calibration_system.py --tool autonomous_lc_calibration --ref-all 0.0 --collect-time 10
364 choices=list(TOOL_REGISTRY.keys()),
365 help=
"Calibration tool to launch",
369 choices=[
"test",
"calibration",
"flight",
"safe"],
370 help=
"Operation mode (for GUI tools)",
373 "--skip-health-check",
375 help=
"Skip system health checks",
382 help=f
"UDP port for board data (default: {_default_udp_port()} from config.toml)",
387 help=
"Filter packets to this board IP (e.g. 192.168.2.101)",
393 help=
"Set reference value for all channels (PSI / °C / lbs)",
399 help=
"Collection duration in seconds (default: 5)",
405 help=
"Sensor channel (repeatable, paired with --ref-value)",
411 help=
"Reference value per sensor (repeatable, paired with --sensor)",
417 help=
"Run autonomous calibration in interactive CLI mode",
422 help=
"List all available calibration tools and exit",
425 args = parser.parse_args()
428 print(
"\n" +
"=" * 80)
431 ██████╗ █████╗ ██╗ ██╗██████╗ ██████╗ █████╗ ████████╗██╗ ██████╗ ███╗ ██╗
432 ██╔════╝ ██╔══██╗██║ ██║██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██║██╔═══██╗████╗ ██║
433 ██║ ███╗███████║██║ ██║██████╔╝██████╔╝███████║ ██║ ██║██║ ██║██╔██╗ ██║
434 ██║ ██║██╔══██║██║ ██║██╔══██╗██╔══██╗██╔══██║ ██║ ██║██║ ██║██║╚██╗██║
435 ╚██████╔╝██║ ██║███████╗██║██████╔╝██║ ██║██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║
436 ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝
439 print(
" SENSOR CALIBRATION SYSTEM v5.0")
440 print(
" PT · TC · RTD · LC | UDP Pipeline")
441 print(
"=" * 80 +
"\n")
444 print_config_summary()
451 if not args.skip_health_check:
454 "❌ System health checks failed. Use --skip-health-check to override."
458 logger.warning(
"⚠️ Health checks skipped")
461 tool_name = args.tool
465 info = TOOL_REGISTRY[tool_name]
466 script_path = CALIBRATION_DIR / info[
"script"]
468 if not script_path.exists():
469 logger.error(f
"❌ Tool script not found: {script_path}")
477 print(f
"🚀 Launching: {tool_name}")
478 print(f
" Script: {script_path}")
479 print(f
" Sensor: {info['sensor_type']}")
480 print(f
" Type: {'GUI' if info.get('gui') else 'CLI / Autonomous'}")
481 if not info.get(
"gui"):
482 print(f
" UDP: 0.0.0.0:{args.udp_port}")
483 board = args.board_ip
or _board_ip_for(info[
"sensor_type"])
or "any"
485 f
" Board: {board} (from {'CLI' if args.board_ip else 'config.toml'})"
487 print(f
" Cmd: {' '.join(cmd)}")
491 result = subprocess.run(cmd, cwd=str(CALIBRATION_DIR))
492 return result.returncode
493 except KeyboardInterrupt:
494 logger.info(
"\n🛑 Calibration system stopped by user")
496 except Exception
as e:
497 logger.error(f
"❌ Failed to launch calibration tool: {e}")
500 traceback.print_exc()