e4812e9b44
Cherry-pick of ab28cb7 onto main (was missing from main branch history). Python: - arautopilot/core/sensor_config.py: RudderSensorConfig + DualRudderSensorConfig (AS5048A SPI / potentiometer, dual with cross-validation thresholds) - arautopilot/core/vessel_config.py: add sensors: DualRudderSensorConfig field - arautopilot/studio/compiler/appack.py: .appack compiler (ZIP with manifest.json + project.yaml + firmware_config.h + install_notes.txt) - arautopilot/studio/editors/project_editor.py: full project config editor (vessel / actuator / sensors / PID, RBAC-gated fields, save/load .yaml/.json) - arautopilot/studio/main_window.py: wire ProjectEditorWidget into Project tab - tests: test_sensor_config.py (11 tests) + test_appack_compiler.py (10 tests) Firmware: - hal/rudder_actuator_iface.h: IRudderActuator abstract interface - hal/rudder_actuator_hydraulic.h: reversible hydraulic pump (LEDC PWM) - hal/rudder_actuator_electric.h: reversible DC motor + deadband compensation - hal/rudder_actuator_factory.h: build-time type selection via AR_ACTUATOR_TYPE Tests: 483 passed (was 462, +21 Sprint 4) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
108 lines
3.5 KiB
Python
108 lines
3.5 KiB
Python
"""Studio main window (PySide6) -- Sprint 2.5.
|
|
|
|
Three areas:
|
|
|
|
- Sidebar (left) -- user + role + capabilities they hold.
|
|
- Central tab area -- Flash Console (Sprint 2.5) + placeholders for the
|
|
project configurator that lands in Sprint 4.
|
|
- Status bar -- session info + audit log path.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from PySide6.QtCore import Qt
|
|
from PySide6.QtWidgets import (
|
|
QLabel,
|
|
QListWidget,
|
|
QMainWindow,
|
|
QSplitter,
|
|
QStatusBar,
|
|
QTabWidget,
|
|
QTextEdit,
|
|
QVBoxLayout,
|
|
QWidget,
|
|
)
|
|
|
|
from arautopilot.core.rbac import capabilities_of
|
|
from arautopilot.studio.editors.project_editor import ProjectEditorWidget
|
|
from arautopilot.studio.flash_console import FlashConsoleWidget
|
|
from arautopilot.studio.session import Session
|
|
from arautopilot.version import __version__
|
|
|
|
|
|
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(1100, 700)
|
|
|
|
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([260, 840])
|
|
self.setCentralWidget(splitter)
|
|
|
|
status = QStatusBar(self)
|
|
status.showMessage(f"Audit log: {session.audit.path}")
|
|
self.setStatusBar(status)
|
|
|
|
# ----- UI ------------------------------------------------------------
|
|
def _build_sidebar(self) -> QWidget:
|
|
w = QWidget()
|
|
layout = QVBoxLayout(w)
|
|
layout.setContentsMargins(8, 8, 8, 8)
|
|
layout.addWidget(QLabel(
|
|
f"<b>{self._session.user.display_name}</b><br/>"
|
|
f"<i>{self._session.role.value}</i>"
|
|
))
|
|
layout.addWidget(QLabel("<b>Capabilities</b>"))
|
|
caps = QListWidget()
|
|
for cap in sorted(capabilities_of(self._session.role), key=lambda c: c.value):
|
|
caps.addItem(cap.value)
|
|
layout.addWidget(caps, stretch=1)
|
|
return w
|
|
|
|
def _build_central(self) -> QWidget:
|
|
tabs = QTabWidget()
|
|
|
|
tabs.addTab(self._build_overview_tab(), "Overview")
|
|
tabs.addTab(FlashConsoleWidget(self._session), "Flash Console")
|
|
tabs.addTab(ProjectEditorWidget(self._session), "Project")
|
|
tabs.addTab(self._placeholder_tab(
|
|
"Telemetry -- Sprint 4.\n\n"
|
|
"Live Modbus telemetry from the connected AR-NMEA-IO board."
|
|
), "Telemetry")
|
|
return tabs
|
|
|
|
def _build_overview_tab(self) -> QWidget:
|
|
w = QWidget()
|
|
layout = QVBoxLayout(w)
|
|
layout.addWidget(QLabel(
|
|
"<h2>AR-Autopilot Studio</h2>"
|
|
"<p>Welcome. Use the <b>Flash Console</b> tab to compile and "
|
|
"flash firmware to an AR-NMEA-IO board.</p>"
|
|
"<p>The <b>Project</b> tab (Sprint 4) will let you configure a "
|
|
"vessel and produce a deployable <code>.appack</code>.</p>"
|
|
"<p>Every action you take is recorded in the audit log "
|
|
"(see status bar at the bottom).</p>"
|
|
))
|
|
layout.addStretch(1)
|
|
return w
|
|
|
|
def _placeholder_tab(self, text: str) -> QWidget:
|
|
w = QWidget()
|
|
layout = QVBoxLayout(w)
|
|
edit = QTextEdit()
|
|
edit.setReadOnly(True)
|
|
edit.setPlainText(text)
|
|
layout.addWidget(edit)
|
|
return w
|