Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c029d8c
feat(ptp-sv): define bounded on-board lock flight-recorder diagnostic…
masarray Oct 20, 2026
cf2c972
feat(ptp-sv): atomic 16-transition flight recorder and reason-coded l…
masarray Oct 20, 2026
31296fd
feat(ptp-sv): record exact publisher fault cause and 1Hz measured pha…
masarray Oct 20, 2026
8a158fe
feat(firmware): read-only PTP DIAG exposes last lock causes, phase, M…
masarray Oct 20, 2026
81ce39e
feat(studio): expose board SV lock diagnostic snapshot property
masarray Oct 20, 2026
1c50f95
feat(studio): capture internal SV lock causes from verified serial so…
masarray Oct 20, 2026
323ad83
feat(studio): add supervisor-owned read-only lock inspection
masarray Oct 20, 2026
99fe071
feat(studio): query firmware flight recorder without disrupting START…
masarray Oct 20, 2026
be6a545
feat(studio): show internal SV lock causes and snapshot on PTP timing…
masarray Oct 20, 2026
d1afa1c
test(ptp-sv): verify lock-loss reason, state, ring wrap, and diagnost…
masarray Oct 20, 2026
d4b79ed
test(studio): enforce native firmware lock recorder accessible in Tim…
masarray Oct 20, 2026
e6eeb4f
fix(ptp-sv): attribute role handoff/STOP loss to actual PTP stop
masarray Oct 20, 2026
b251278
style(ptp-sv): explicit size_t include for fixed flight ring
masarray Oct 20, 2026
1de600b
feat(ptp-sv): automatically surface lock transition causes from 1Hz t…
masarray Oct 20, 2026
93400fe
fix(studio): auto-surface measured firmware lock cause and invalidate…
masarray Oct 20, 2026
0ea81a3
feat(studio): expose last reported firmware sync-loss cause as passiv…
masarray Oct 20, 2026
f6162be
feat(studio): retain latest firmware revocation event without stale b…
masarray Oct 20, 2026
52d34ed
feat(studio-capture): correlate on-board lock cause with passive PTP+…
masarray Oct 20, 2026
24dfa68
test(studio): require on-board lock fault included in passive Capture…
masarray Oct 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions apps/arstack_studio/qml/PtpExpertPanel.qml
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,18 @@ SurfacePanel {
enabled: panel.session && panel.device.deviceVerified && !panel.session.updatingFirmware
onClicked: panel.session.requestPtpRefresh()
}
CalmButton {
theme: panel.theme
uiFont: panel.uiFont
text: "Inspect SV lock"
enabled: panel.session && panel.device.deviceVerified &&
!panel.session.updatingFirmware
onClicked: {
if (!panel.session.requestSyncDiagnostics() && panel.controller)
panel.controller.showMessage(
"SV lock inspection is waiting for an active configuration transaction to finish.", true)
}
}
CalmButton {
theme: panel.theme
uiFont: panel.uiFont
Expand All @@ -321,6 +333,47 @@ SurfacePanel {
}
}

Rectangle {
Layout.fillWidth: true
radius: 8
color: panel.theme.surface2
border.width: 1
border.color: panel.theme.lineSoft
implicitHeight: flightContent.implicitHeight + 24
ColumnLayout {
id: flightContent
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 12
spacing: 6
Label {
Layout.fillWidth: true
text: "SV LOCK · INTERNAL FLIGHT RECORDER"
color: panel.theme.text
font.family: panel.uiFont
font.pixelSize: 10
font.weight: Font.DemiBold
}
Label {
Layout.fillWidth: true
text: panel.device.syncDiagnostics
color: panel.theme.textSoft
font.family: panel.monoFont
font.pixelSize: 9
wrapMode: Text.WrapAnywhere
}
Label {
Layout.fillWidth: true
text: "Board-internal reasons, not an independently certified PTP/IED lock."
color: panel.theme.muted
font.family: panel.uiFont
font.pixelSize: 9
wrapMode: Text.WordWrap
}
}
}

Rectangle {
Layout.fillWidth: true
implicitHeight: 102
Expand Down
3 changes: 2 additions & 1 deletion apps/arstack_studio/qml/SvCapturePanel.qml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ Rectangle {
"ptp_offset_ns": panel.device ? panel.device.ptpOffsetNs : "",
"device_fps": panel.device ? panel.device.fps : "",
"device_missed": panel.device ? panel.device.missed : "",
"device_tx_failures": panel.device ? panel.device.txFailures : ""
"device_tx_failures": panel.device ? panel.device.txFailures : "",
"onboard_sync_flight_UNVERIFIED": panel.device ? panel.device.syncFlightLatest : ""

}
}
Expand Down
27 changes: 27 additions & 0 deletions apps/arstack_studio/src/DeviceController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1682,6 +1682,29 @@ void DeviceController::processLine(const QString& rawLine) {
if (line.isEmpty()) return;
appendLog(QStringLiteral("←"), line);

// Explicit on-board flight recorder response. Do not poll from the
// UI render path or treat historical reports as new lock evidence.
const auto flightPos = line.indexOf(QStringLiteral("SVSYNC "));
if (flightPos >= 0) {
const QString flightLine = line.mid(flightPos).trimmed();
if (flightLine.startsWith(QStringLiteral("SVSYNC FLIGHT "))) {
syncFlightLatest_ = flightLine;
// Firmware emits one bounded fault summary from its telemetry
// task when a lock edge occurs. Surface it even if Inspect has
// not been clicked, never as a declaration of physical PASS.
syncDiagnostics_ = flightLine + QLatin1Char('\n') + syncDiagnostics_;
}
if (flightLine.startsWith(QStringLiteral("SVSYNC DIAG ")))
syncDiagnostics_ = flightLine;
else if (flightLine.startsWith(QStringLiteral("SVSYNC CAUSE ")) ||
flightLine.startsWith(QStringLiteral("SVSYNC EVENT ")))
syncDiagnostics_ += QLatin1Char('\n') + flightLine;
// END marks a complete snapshot, never a certification.
if (syncDiagnostics_.size() > 6000)
syncDiagnostics_ = syncDiagnostics_.right(6000);
emit syncDiagnosticsChanged();
}

// Only the owner of the current session+boot may advance commit -> readback -> START.
// Regexes search through ESP-IDF log prefixes; all token fields are matched strictly.
const auto scenarioCommitted = kScenarioCommitExpression.match(line);
Expand Down Expand Up @@ -1946,6 +1969,10 @@ void DeviceController::resetTelemetry() {

void DeviceController::resetPtpState() {
resetPtpStartup();
syncDiagnostics_ = QStringLiteral(
"No current-board SV lock evidence. Select Inspect SV lock after verification.");
syncFlightLatest_.clear();
emit syncDiagnosticsChanged();
ptpAvailable_ = false;
ptpRunning_ = false;
ptpStatus_ = QStringLiteral("Waiting for device");
Expand Down
8 changes: 8 additions & 0 deletions apps/arstack_studio/src/DeviceController.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ class DeviceController : public QObject {
Q_PROPERTY(QString portName READ portName NOTIFY connectedChanged)
Q_PROPERTY(QString lastError READ lastError NOTIFY lastErrorChanged)
Q_PROPERTY(QString logText READ logText NOTIFY logTextChanged)
Q_PROPERTY(QString syncDiagnostics READ syncDiagnostics NOTIFY syncDiagnosticsChanged)
Q_PROPERTY(QString syncFlightLatest READ syncFlightLatest NOTIFY syncDiagnosticsChanged)
Q_PROPERTY(QString fps READ fps NOTIFY telemetryChanged)
Q_PROPERTY(QString missed READ missed NOTIFY telemetryChanged)
Q_PROPERTY(QString txFailures READ txFailures NOTIFY telemetryChanged)
Expand Down Expand Up @@ -139,6 +141,8 @@ class DeviceController : public QObject {
[[nodiscard]] QString portName() const;
[[nodiscard]] QString lastError() const;
[[nodiscard]] QString logText() const;
[[nodiscard]] QString syncDiagnostics() const { return syncDiagnostics_; }
[[nodiscard]] QString syncFlightLatest() const { return syncFlightLatest_; }
[[nodiscard]] QString fps() const;
[[nodiscard]] QString missed() const;
[[nodiscard]] QString txFailures() const;
Expand Down Expand Up @@ -259,6 +263,7 @@ class DeviceController : public QObject {
return sendCommand(QStringLiteral("PROFILE PTPROLE %1").arg(role));
}

bool sendPtpDiagnostics() { return sendCommand(QStringLiteral("PTP DIAG")); }
bool sendSmpSynchShow() {
return sendCommand(QStringLiteral("PROFILE SHOW"));
}
Expand Down Expand Up @@ -287,6 +292,7 @@ class DeviceController : public QObject {
void runningChanged();
void lastErrorChanged();
void logTextChanged();
void syncDiagnosticsChanged();
void telemetryChanged();
void profileStateChanged();
void profileDeploymentProgress();
Expand Down Expand Up @@ -389,6 +395,8 @@ class DeviceController : public QObject {
DeviceIdentity identity_;
QString lastError_;
QString logText_;
QString syncDiagnostics_{QStringLiteral("Select Inspect SV lock to read on-board causes.")};
QString syncFlightLatest_;
QString fps_{QStringLiteral("—")};
QString missed_{QStringLiteral("—")};
QString txFailures_{QStringLiteral("—")};
Expand Down
12 changes: 12 additions & 0 deletions apps/arstack_studio/src/SmartSessionController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,16 @@ bool SmartSessionController::requestPtpRefresh() {
return ptp && sync;
}

bool SmartSessionController::requestSyncDiagnostics() {
// Read-only operator request; serialize behind the existing device
// session. Never interleave with START/CFG or firmware port handoff.
if (!deviceControlAvailable() || device_ == nullptr ||
device_->ptpStarting() || device_->profileDeploying() ||
profileSyncStage_ == ProfileSyncStage::deploying ||
device_->scenarioApplying() || updateRequested_) return false;
return device_->sendPtpDiagnostics();
}

bool SmartSessionController::requestPtpRole(const QString& role) {
return deviceControlAvailable() && device_ != nullptr && device_->setPtpRole(role);
}
Expand Down Expand Up @@ -973,6 +983,8 @@ QString SmartSessionController::diagnosticsText() const {
add(QStringLiteral("SMV missed reported"), device_->missed());
add(QStringLiteral("SMV TX failures reported"), device_->txFailures());
add(QStringLiteral("PTP status reported"), device_->ptpStatus());
add(QStringLiteral("SV sync internal flight recorder (not physical proof)"),
device_->syncDiagnostics());
add(QStringLiteral("PTP role reported"), device_->ptpRole());
add(QStringLiteral("PTP discipline reported"), device_->ptpDiscipline());
add(QStringLiteral("SMPSYNCH value reported"), device_->smpSynchValue());
Expand Down
1 change: 1 addition & 0 deletions apps/arstack_studio/src/SmartSessionController.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ class SmartSessionController : public QObject {
double clipPercent);
Q_INVOKABLE bool requestZero();
Q_INVOKABLE bool requestPtpRefresh();
Q_INVOKABLE bool requestSyncDiagnostics();
Q_INVOKABLE bool requestPtpRole(const QString& role);
Q_INVOKABLE bool requestSmpSynch(const QString& mode);
Q_INVOKABLE bool requestConfigurePtp(const QVariantMap& profile);
Expand Down
20 changes: 20 additions & 0 deletions apps/arstack_studio/tests/check_operator_workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,26 @@ def main() -> None:
"QDesktopServices::openUrl(" in capture_cpp and
"QString findDumpcap()" in capture_cpp,
"Studio capture results must derive from production wire decoder with click-only setup")
ptp_src = source("PtpExpertPanel.qml")
ptp_fw = (STUDIO.parent.parent / "embedded/esp32p4_smv_injector/main/live_control.cpp").read_text(encoding="utf-8")
ptp_sync = (STUDIO.parent.parent / "embedded/esp32p4_smv_injector/main/smp_synch_lab.cpp").read_text(encoding="utf-8")
dev_h = (STUDIO / "src/DeviceController.hpp").read_text(encoding="utf-8")
dev_cpp = (STUDIO / "src/DeviceController.cpp").read_text(encoding="utf-8")
session_h = (STUDIO / "src/SmartSessionController.hpp").read_text(encoding="utf-8")
require('text: "Inspect SV lock"' in ptp_src and
"panel.session.requestSyncDiagnostics()" in ptp_src and
"panel.device.syncDiagnostics" in ptp_src and
'std::strcmp(subcommand, "DIAG")' in ptp_fw and
'"SVSYNC EVENT seq=' in ptp_fw and
"record_transition(" in ptp_sync and
"Q_PROPERTY(QString syncDiagnostics" in dev_h and
"SVSYNC DIAG " in dev_cpp and
"requestSyncDiagnostics()" in session_h and
"onboard_sync_flight_UNVERIFIED" in capture_ui and
"syncFlightLatest" in dev_h and
"syncFlightLatest_ = flightLine" in dev_cpp,
"Studio must expose the physical firmware's internal, reason-coded "
"lock flight recorder in Time Sync and correlate it passively with Capture")
print("Studio operator workspace contract: PASS")


Expand Down
Loading
Loading