Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions apps/arstack_studio/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ set(ARSTACK_STUDIO_CORE_SOURCES
src/M4MultiSvUploadTransaction.hpp
src/M4ThreeMuScenarioApply.cpp
src/M4ThreeMuScenarioApply.hpp
src/M4ThreeMuBoardReadback.cpp
src/M4ThreeMuBoardReadback.hpp
src/DeviceIoWorker.cpp
src/DeviceIoWorker.hpp
src/DeviceController.cpp
Expand Down Expand Up @@ -250,6 +252,8 @@ qt_add_executable(arstack_m4_upload_harness
src/M4MultiSvUploadTransaction.hpp
src/M4ThreeMuScenarioApply.cpp
src/M4ThreeMuScenarioApply.hpp
src/M4ThreeMuBoardReadback.cpp
src/M4ThreeMuBoardReadback.hpp
src/SclProfileModel.cpp
src/SclProfileModel.hpp
)
Expand Down
69 changes: 67 additions & 2 deletions apps/arstack_studio/qml/Main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -1335,7 +1335,9 @@ ApplicationWindow {

RowLayout {
id: m4PaginationStrip
visible: sclProfiles.m4DraftSupported || m4Pages.active
visible: sclProfiles.m4DraftSupported || m4Pages.active ||
(workflowBar.session && workflowBar.session.canInspectMultiSvBoard) ||
device.m4BoardInspecting || device.m4BoardSnapshots.length === 3
Layout.fillWidth: true
Layout.preferredHeight: visible ? 31 : 0
spacing: 5
Expand Down Expand Up @@ -1421,14 +1423,77 @@ ApplicationWindow {
}
}
Button {
enabled: !device.m4Deploying && !device.m4ScenarioApplying
visible: device.deviceVerified || device.m4BoardInspecting ||
device.m4BoardSnapshots.length === 3
enabled: workflowBar.session && workflowBar.session.canInspectMultiSvBoard &&
!workflowBar.startQueued
text: device.m4BoardInspecting ? "Reading…" : "Inspect board"
font.family: root.uiFont
font.pixelSize: 10
onClicked: {
const ok = workflowBar.session.requestInspectMultiSvBoard()
root.showMessage(ok
? "Reading MU1/2/3 from the actual board; editor drafts remain unchanged."
: "Board inspection unavailable; check identity, serial ownership and firmware.", !ok)
}
}
Button {
enabled: (m4Pages.active || sclProfiles.m4DraftSupported) &&
!device.m4Deploying && !device.m4ScenarioApplying
text: m4Pages.active ? "Close MU pages" : "Open MU pages"
font.family: root.uiFont
font.pixelSize: 10
onClicked: m4Pages.active ? root.m4CloseDraft() : root.m4OpenDraft()
}
}

RowLayout {
visible: device.m4BoardSnapshots.length === 3
Layout.fillWidth: true
Layout.preferredHeight: visible ? 24 : 0
spacing: 8
Repeater {
model: device.m4BoardSnapshots
delegate: Rectangle {
required property var modelData
Layout.fillWidth: true
implicitHeight: 23
radius: 5
color: studioTheme.surface2
border.color: studioTheme.lineSoft
border.width: 1
HoverHandler { id: bankHover }
ToolTip.visible: bankHover.hovered
ToolTip.delay: 500
ToolTip.text: {
const shape = modelData.ct
const channels = modelData.channels
const rows = channels.map(function(ch) {
return ch.id + " = " + ch.counts + " counts, phase " +
ch.phaseMdeg + " mdeg, Q=" + ch.quality +
", " + (ch.enabled ? "enabled" : "disabled")
}).join("\n")
return "BOARD READBACK · MU" + modelData.slot +
"\nGen " + modelData.generation + " · hash " + modelData.hash +
"\nCT shaping: " + (shape.enabled ? "ON" : "OFF") +
" · DC " + shape.dcPermille + " ‰ · H " +
shape.harmonicPermille + " ‰ / order " + shape.order +
" · clip " + shape.clipPermille + " ‰\n" + rows
}
Label {
anchors.centerIn: parent
text: "BOARD MU" + modelData.slot + " · " +
(modelData.frequencyMilliHz / 1000).toFixed(3) +
" Hz · G" + modelData.generation +
" · TX " + (modelData.txEnabled ? "ON" : "OFF")
color: studioTheme.green
font.family: root.uiFont
font.pixelSize: 9
}
}
}
}

SplitView {
id: shellSplit
Layout.fillWidth: true
Expand Down
20 changes: 20 additions & 0 deletions apps/arstack_studio/src/DeterministicSessionHarness.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class DeterministicSessionHarness final {
{"M4 upload STOP revokes transaction proof before serial STOP", m4StopCancelsUpload()},
{"M4 per-MU CFG STOP aborts serial queue without V1 START", m4PerMuScenarioStopGuard()},
{"M4 timeout retires unsent exclusive CFG without unsolicited STOP", m4RetireExclusiveQueueWithoutStop()},
{"M4 board inspection defers refresh but PTP STOP preempts it", m4BoardInspectionPtpSafetyPreempts()},
{"legacy bare SV START refuses stale eight-channel state", legacyBareStartFailsClosed()},
{"SV commit ACK/readback START chain is generation bound", svTransactionAckChain()},
{"SV stale ACK and mismatched firmware readback fail closed", svTransactionNegativeAcks()},
Expand Down Expand Up @@ -2139,6 +2140,25 @@ class DeterministicSessionHarness final {
!worker.svStopPending_ && !worker.ptpStopPending_;
}

static bool m4BoardInspectionPtpSafetyPreempts() {
Fixture fixture;
if (!fixture.profileReady) return false;
seedVerified(fixture, identity(), QStringLiteral("COM7"), false);
auto& d=fixture.device;
d.m4BoardInspecting_=true;
d.m4BoardSession_=d.sessionGeneration_;
d.m4BoardTimer_.start(6000);
d.deferredPtpShow_=true;
d.flushDeferredReadbacks();
const bool deferred=d.deferredPtpShow_ && d.m4BoardInspecting_;
// Even while the publisher is stopped, safety STOP must terminate the
// inspection before the priority command reaches the sole worker.
static_cast<void>(d.sendCommand(QStringLiteral("PTP STOP")));
return deferred && !d.m4BoardInspecting_ && !d.m4BoardTimer_.isActive() &&
d.m4BoardStatus().contains(QStringLiteral("STOP")) &&
d.deferredPtpShow_ && d.m4BoardSnapshots_.isEmpty();
}

static bool workerTeardownCompletes() {
bool profileReady = false;
{
Expand Down
Loading
Loading