"""Studio main window (PySide6). Five tabs: Overview — welcome + quick-start guide Flash Console — compile & flash ESP32 firmware via PlatformIO Project — vessel configuration + .appack compiler Telemetría — live $PARP STATUS charts from the AR-Concentrador Instalar J6412 — build USB pendrive installer images Sidebar shows the logged-in user, role, and RBAC capabilities. """ from __future__ import annotations from pathlib import Path from PySide6.QtCore import Qt from PySide6.QtGui import QFont, QPixmap from PySide6.QtWidgets import ( QFrame, QHBoxLayout, QLabel, QListWidget, QMainWindow, QMessageBox, QPushButton, QSplitter, QStatusBar, QTabWidget, QVBoxLayout, QWidget, ) from arautopilot.core.rbac import capabilities_of from arautopilot.studio.ar_style import ACCENT_MID, GLOW, NAVY, TEXT_MUTED from arautopilot.studio.editors.project_editor import ProjectEditorWidget from arautopilot.studio.flash_console import FlashConsoleWidget from arautopilot.studio.installer_widget import InstallerWidget from arautopilot.studio.session import Session from arautopilot.studio.telemetry_widget import TelemetryWidget from arautopilot.version import __version__ REPO_ROOT = Path(__file__).resolve().parents[2] _LOGO_PATH = REPO_ROOT / "display" / "assets" / "images" / "ar_logo_full.png" class StudioMainWindow(QMainWindow): """Top-level Studio window.""" def __init__(self, session: Session) -> None: super().__init__() self._session = session self.setWindowTitle( f"AR-Autopilot Studio v{__version__} — " f"{session.user.display_name} ({session.role.value})" ) self.resize(1200, 780) splitter = QSplitter(Qt.Orientation.Horizontal) splitter.addWidget(self._build_sidebar()) splitter.addWidget(self._build_central()) splitter.setStretchFactor(0, 0) splitter.setStretchFactor(1, 1) splitter.setSizes([240, 960]) self.setCentralWidget(splitter) status = QStatusBar(self) status.showMessage(f"Audit log: {session.audit.path}") self.setStatusBar(status) # ── Sidebar ─────────────────────────────────────────────────────────────── def _build_sidebar(self) -> QWidget: w = QWidget() w.setMaximumWidth(260) layout = QVBoxLayout(w) layout.setContentsMargins(10, 14, 10, 10) layout.setSpacing(10) # Logo if _LOGO_PATH.exists(): logo_lbl = QLabel() px = QPixmap(str(_LOGO_PATH)).scaledToWidth( 160, Qt.TransformationMode.SmoothTransformation ) logo_lbl.setPixmap(px) logo_lbl.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(logo_lbl) else: brand_lbl = QLabel("AR Electronics") brand_lbl.setStyleSheet( f"color:{GLOW}; font-size:16px; font-weight:bold; letter-spacing:2px;" ) brand_lbl.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(brand_lbl) # Separator sep = QFrame() sep.setFrameShape(QFrame.Shape.HLine) sep.setStyleSheet(f"color:{ACCENT_MID};") layout.addWidget(sep) # User info role_label = QLabel( f"" f"{self._session.user.display_name}
" f"" f"{self._session.role.value}" ) role_label.setTextFormat(Qt.TextFormat.RichText) role_label.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(role_label) sep2 = QFrame() sep2.setFrameShape(QFrame.Shape.HLine) sep2.setStyleSheet(f"color:{ACCENT_MID};") layout.addWidget(sep2) # Capabilities cap_title = QLabel("Permisos") cap_title.setStyleSheet( f"color:{ACCENT_MID}; font-weight:bold; font-size:10px; letter-spacing:1px;" ) layout.addWidget(cap_title) caps = QListWidget() caps.setStyleSheet("font-size:10px;") for cap in sorted(capabilities_of(self._session.role), key=lambda c: c.value): caps.addItem(cap.value) layout.addWidget(caps, stretch=1) # Version stamp ver_lbl = QLabel(f"Studio v{__version__}") ver_lbl.setStyleSheet(f"color:{TEXT_MUTED}; font-size:10px;") ver_lbl.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(ver_lbl) return w # ── Central tabs ────────────────────────────────────────────────────────── def _build_central(self) -> QWidget: tabs = QTabWidget() tabs.addTab(self._build_overview_tab(), "🧭 Overview") tabs.addTab(FlashConsoleWidget(self._session), "⚡ Flash ESP32") tabs.addTab(ProjectEditorWidget(self._session), "📋 Proyecto") tabs.addTab(TelemetryWidget(self._session), "📡 Telemetría") tabs.addTab(InstallerWidget(self._session), "💾 Instalar J6412") return tabs # ── Overview tab ────────────────────────────────────────────────────────── def _build_overview_tab(self) -> QWidget: w = QWidget() layout = QVBoxLayout(w) layout.setContentsMargins(24, 20, 24, 20) title = QLabel("AR-Autopilot Studio") title.setFont(QFont("Segoe UI", 20, QFont.Weight.Bold)) title.setStyleSheet(f"color:{GLOW}; letter-spacing:2px;") layout.addWidget(title) subtitle = QLabel(f"v{__version__} — Herramienta de integración para el sistema AR-Autopilot") subtitle.setStyleSheet(f"color:{TEXT_MUTED}; font-size:12px;") layout.addWidget(subtitle) sep = QFrame() sep.setFrameShape(QFrame.Shape.HLine) sep.setStyleSheet(f"color:{ACCENT_MID}; margin: 10px 0;") layout.addWidget(sep) guide = QLabel( "" f"" f"" "" f"" f"" "" f"" f"" "" f"" f"" "
Flash ESP32
" f"Compila y flashea el firmware al AR-Concentrador " f"o al autopilot ESP32 via USB.
📋Proyecto
" f"Configura el buque (tipo, dimensiones, actuador, " f"sensores, ganancias PID) y genera un paquete .appack de despliegue.
📡Telemetría
" f"Conecta al AR-Concentrador por puerto COM y ve " f"en tiempo real el rumbo, setpoint y ángulo de timón.
💾Instalar J6412
" f"Genera un pendrive USB que instala AR-ECDIS y " f"AR-Autopilot Display en el mini PC J6412 con activación de licencia online." "
" ) guide.setTextFormat(Qt.TextFormat.RichText) guide.setWordWrap(True) layout.addWidget(guide) # AR Display Manager quick launch sep2 = QFrame() sep2.setFrameShape(QFrame.Shape.HLine) sep2.setStyleSheet(f"color:{ACCENT_MID}; margin: 10px 0;") layout.addWidget(sep2) dm_row = QHBoxLayout() dm_label = QLabel( f"🖥 AR Display Manager
" f"" "Gestiona cuál app aparece en cada monitor del J6412 " "(hasta 4 pantallas simultáneas)." ) dm_label.setTextFormat(Qt.TextFormat.RichText) dm_label.setWordWrap(True) dm_row.addWidget(dm_label, stretch=1) dm_btn = QPushButton("Lanzar Display Manager") dm_btn.setMinimumWidth(200) dm_btn.clicked.connect(self._launch_display_manager) dm_row.addWidget(dm_btn) layout.addLayout(dm_row) layout.addStretch(1) footer = QLabel( f"" "AR Electronics — Todos los derechos reservados." ) footer.setTextFormat(Qt.TextFormat.RichText) footer.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(footer) return w # ── Display Manager launcher ────────────────────────────────────────────── def _launch_display_manager(self) -> None: import subprocess import sys as _sys launcher = REPO_ROOT / "display_manager_main.py" if not launcher.exists(): QMessageBox.warning( self, "Not found", f"display_manager_main.py not found at:\n{launcher}", ) return try: subprocess.Popen( [_sys.executable, str(launcher)], creationflags=( subprocess.CREATE_NEW_PROCESS_GROUP if _sys.platform == "win32" else 0 ), ) except Exception as exc: QMessageBox.critical(self, "Launch failed", str(exc))