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
7 changes: 7 additions & 0 deletions apps/arstack_studio/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,13 @@ qt_add_qml_module(arstack_studio
RESOURCES
assets/InterVariable.ttf
assets/INTER_LICENSE.txt
assets/material_dark/play_arrow.svg
assets/material_dark/stop.svg
assets/material_dark/folder_open.svg
assets/material_dark/dashboard.svg
assets/material_dark/tune.svg
assets/material_dark/monitor_heart.svg
assets/material_dark/settings.svg
assets/MATERIAL_ICONS_LICENSE.txt
assets/material/close.svg
assets/material/open_in_new.svg
Expand Down
1 change: 1 addition & 0 deletions apps/arstack_studio/assets/material_dark/dashboard.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/arstack_studio/assets/material_dark/folder_open.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/arstack_studio/assets/material_dark/monitor_heart.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/arstack_studio/assets/material_dark/play_arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/arstack_studio/assets/material_dark/settings.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/arstack_studio/assets/material_dark/stop.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/arstack_studio/assets/material_dark/tune.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
88 changes: 53 additions & 35 deletions apps/arstack_studio/qml/Main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ ApplicationWindow {
function markScenarioDirty() {
if (restoringScenario) return
saveScenarioTimer.restart()
if (!workflowBar.session || !workflowBar.session.liveControlReady)
// While STOPPED, a new edit belongs to the next supervised Start batch.
// Only a genuinely RUNNING board receives incremental live SETs.
if (!workflowBar.injectionRunning)
pendingApply = true
}
function restoreLocalScenario() {
Expand Down Expand Up @@ -132,6 +134,7 @@ ApplicationWindow {
property real activePhase: 0.0
property real activeQuality: 0
property string transientMessage: ""
property string statusMessage: ""
property bool transientError: false
property bool phasorDockVisible: true
property bool phasorDetached: false
Expand Down Expand Up @@ -197,6 +200,7 @@ ApplicationWindow {
onTriggered: {
root.transientMessage = ""
root.transientError = false
root.statusMessage = ""
}
}

Expand All @@ -222,7 +226,8 @@ ApplicationWindow {
}

function showMessage(message, error) {
transientMessage = message
statusMessage = message
transientMessage = error === true ? message : ""
transientError = error === true
statusHistoryModel.append({
timeText: new Date().toLocaleTimeString(Qt.locale(), "HH:mm:ss"),
Expand Down Expand Up @@ -425,16 +430,13 @@ ApplicationWindow {
: "One or more draft commands could not be queued; retry after checking the board.", !ok)
return ok
}
function requestApplyDraft() {
if (!workflowBar.session || !workflowBar.session.liveControlReady) {
showMessage("Local draft saved; connect a verified ESP32-P4 to apply it.", false)
return false
}
if (workflowBar.injectionRunning) {
showMessage("Stop the publisher before applying an offline draft.", true)
return false
}
return applyAllSignals()
function startScenarioSnapshot() {
var draft = scenarioSnapshot()
draft.currentScale = currentScale
draft.voltageScale = voltageScale
draft.ct = { enabled: ctSaturationEnabled, dc: ctDcOffsetPercent,
harmonic: ctHarmonicPercent, order: ctHarmonicOrder, clip: ctClipPercent }
return draft
}

function setFrequencyValue(value) {
Expand Down Expand Up @@ -565,8 +567,8 @@ ApplicationWindow {
workflowBar.session.requestSetQuality(activeSignal, unsignedValue)
}

Shortcut { sequence: "Ctrl+B"; onActivated: root.balanced() }
Shortcut { sequence: "Ctrl+0"; onActivated: root.zeroAll() }
Shortcut { sequence: "Ctrl+B"; enabled: !workflowBar.startQueued; onActivated: root.balanced() }
Shortcut { sequence: "Ctrl+0"; enabled: !workflowBar.startQueued; onActivated: root.zeroAll() }
Shortcut { sequence: "F5"; enabled: workflowBar.session && workflowBar.session.state !== "RUNNING"; onActivated: workflowBar.requestStart() }
Shortcut { sequence: "F6"; enabled: workflowBar.session && workflowBar.session.state === "RUNNING"; onActivated: workflowBar.requestStop() }

Expand Down Expand Up @@ -630,7 +632,9 @@ ApplicationWindow {
spacing: 8

Label {
text: device.deviceVerified ? device.portName + " · ESP32-P4" : "No device"
text: !device.deviceVerified
? "No ESP32 board detected · Preview mode only"
: device.portName + " · ESP32-P4" + (workflowBar.injectionRunning ? " · Injecting" : " · Ready")
color: device.deviceVerified ? studioTheme.textSoft : studioTheme.muted
font.family: root.uiFont
font.pixelSize: 9
Expand All @@ -644,6 +648,22 @@ ApplicationWindow {
font.pixelSize: 9
}
Item { Layout.fillWidth: true }
Label {
visible: root.statusMessage.length > 0
Layout.maximumWidth: root.compactLayout ? 165 : 300
text: root.statusMessage
elide: Text.ElideRight
color: root.transientError ? studioTheme.amber : studioTheme.textSoft
font.family: root.uiFont
font.pixelSize: 9
}
Label {
visible: device.deviceVerified
text: "PTP " + (device.ptpRunning ? device.ptpRole : "OFF") + " · smpSynch " + device.smpSynchValue
color: device.ptpRunning ? studioTheme.green : studioTheme.muted
font.family: root.uiFont
font.pixelSize: 9
}
Label {
visible: device.deviceVerified
text: "FPS " + device.fps + " MISSED " + device.missed + " TX FAIL " + device.txFailures
Expand Down Expand Up @@ -795,8 +815,8 @@ ApplicationWindow {
id: workflowBar
objectName: "studioWorkflowBar"
Layout.fillWidth: true
Layout.preferredHeight: 54
Layout.minimumHeight: 54
Layout.preferredHeight: 78
Layout.minimumHeight: 78
theme: studioTheme
controller: root
device: device
Expand Down Expand Up @@ -854,7 +874,7 @@ ApplicationWindow {
ColumnLayout {
anchors.fill: parent
anchors.margins: root.compactLayout ? 11 : 13
spacing: 8
spacing: root.compactLayout ? 6 : 8

RowLayout {
Layout.fillWidth: true
Expand All @@ -863,22 +883,14 @@ ApplicationWindow {
Label { text: "4I + 4V Injection"; color: studioTheme.text; font.family: root.uiFont; font.pixelSize: root.compactLayout ? 17 : 18; font.weight: Font.DemiBold }
}
Item { Layout.fillWidth: true }
CalmButton {
theme: studioTheme
uiFont: root.uiFont
iconSource: Qt.resolvedUrl("../assets/material/file_upload.svg")
text: !device.deviceVerified ? "Local draft"
: root.pendingApply ? "Apply to board" : "SET queued"
implicitWidth: 106
tone: device.deviceVerified && root.pendingApply ? "accent" : "normal"
toolTipText: "Local setpoints are separate from physical output. Board last values are not read back."
onClicked: root.requestApplyDraft()
}
// START owns the single ordered scenario transaction.
// No redundant Apply button in the operator workflow.
}

Rectangle {
Layout.fillWidth: true
height: 48
enabled: !workflowBar.startQueued
radius: 8
color: "#0b1219"
border.width: 0
Expand All @@ -891,6 +903,7 @@ ApplicationWindow {
x: 80
y: 45
title: "Frequency · shared across all channels"
commandRows: 4
Action { text: "DC · 0 Hz"; icon.source: Qt.resolvedUrl("../assets/material/block.svg"); onTriggered: root.setWaveformMode("DC") }
Action { text: "50 Hz · nominal"; icon.source: Qt.resolvedUrl("../assets/material/schedule.svg"); onTriggered: root.setFrequencyValue(50) }
Action { text: "60 Hz · nominal"; icon.source: Qt.resolvedUrl("../assets/material/schedule.svg"); onTriggered: root.setFrequencyValue(60) }
Expand Down Expand Up @@ -920,7 +933,7 @@ ApplicationWindow {
id: frequencyField
ToolTip.visible: hovered && !activeFocus
ToolTip.delay: 650
ToolTip.text: "Right-click for DC / 50 Hz / 60 Hz and AC restoration"
ToolTip.text: "Right-click · Shift+F10"
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
Expand Down Expand Up @@ -979,10 +992,12 @@ ApplicationWindow {

RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: root.compactLayout ? 214 : 224
Layout.maximumHeight: root.compactLayout ? 214 : 224
spacing: 9
SignalMatrix {
id: voltageMatrix
enabled: !workflowBar.startQueued
objectName: "studioVoltageMatrix"
Layout.fillWidth: true
Layout.fillHeight: true
Expand All @@ -1001,6 +1016,7 @@ ApplicationWindow {
}
SignalMatrix {
id: currentMatrix
enabled: !workflowBar.startQueued
objectName: "studioCurrentMatrix"
Layout.fillWidth: true
Layout.fillHeight: true
Expand All @@ -1023,8 +1039,8 @@ ApplicationWindow {
StudioTimingTray {
objectName: "studioTimingTray"
Layout.fillWidth: true
Layout.preferredHeight: root.height < 700 ? 112 : 132
Layout.minimumHeight: root.height < 700 ? 112 : 132
Layout.preferredHeight: 84
Layout.minimumHeight: 84
theme: studioTheme
controller: root
device: device
Expand All @@ -1050,6 +1066,7 @@ ApplicationWindow {
}
Rectangle { width: 1; height: 20; color: studioTheme.lineSoft }
QualityEditor {
enabled: !workflowBar.startQueued
Layout.fillWidth: true
theme: studioTheme
controller: root
Expand All @@ -1060,6 +1077,7 @@ ApplicationWindow {
compact: root.compactLayout
}
}
Item { Layout.fillHeight: true }
}
}

Expand Down Expand Up @@ -1089,7 +1107,7 @@ ApplicationWindow {
SplitView.fillWidth: true
SplitView.fillHeight: true
SplitView.minimumHeight: 170
SplitView.preferredHeight: 300
SplitView.preferredHeight: 238
theme: studioTheme
titleText: "Phasor"
statusText: ""
Expand Down Expand Up @@ -1128,7 +1146,7 @@ ApplicationWindow {
SplitView.fillWidth: true
SplitView.fillHeight: true
SplitView.minimumHeight: 170
SplitView.preferredHeight: 300
SplitView.preferredHeight: 328
theme: studioTheme
titleText: "Waveform"
statusText: root.signalFrequency.toFixed(3) + " Hz"
Expand Down
34 changes: 15 additions & 19 deletions apps/arstack_studio/qml/ModernRibbon.qml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Rectangle {
property string uiFont: "Inter"
property bool compact: false

implicitHeight: 52
implicitHeight: 76
radius: 0
color: "transparent"
border.width: 0
Expand Down Expand Up @@ -47,7 +47,7 @@ Rectangle {
spacing: 6

RowLayout {
Layout.preferredWidth: bar.compact ? 112 : 132
Layout.preferredWidth: bar.compact ? 105 : 124
Layout.alignment: Qt.AlignVCenter
spacing: 8

Expand Down Expand Up @@ -80,20 +80,18 @@ Rectangle {
}
}

Rectangle { width: 1; height: 24; color: bar.theme.lineSoft }
Rectangle { width: 1; height: 42; color: bar.theme.lineSoft }

RibbonAction {
theme: bar.theme
uiFont: bar.uiFont
text: !bar.device.deviceVerified ? "Preview" : "Start"
text: bar.workflow.startQueued ? "Starting" : "Start"
tone: !bar.device.deviceVerified ? "accent"
: bar.session.startReady ? "success" : "neutral"
iconSource: Qt.resolvedUrl("../assets/material/play_arrow.svg")
enabled: !bar.workflow.injectionRunning && !bar.controller.offlinePreviewRunning
enabled: !bar.workflow.injectionRunning && !bar.workflow.startQueued && !bar.controller.offlinePreviewRunning
&& !bar.session.updatingFirmware && !bar.session.updateNeedsBootloaderHelp
toolTipText: !bar.device.deviceVerified
? "Animate local setpoint preview only; no SV frames are transmitted."
: bar.workflow.startReason()
toolTipText: "Start"
onClicked: {
if (!bar.device.deviceVerified) {
bar.controller.offlinePreviewRunning = true
Expand All @@ -110,21 +108,21 @@ Rectangle {
iconSource: Qt.resolvedUrl("../assets/material/stop.svg")
enabled: (bar.workflow.injectionRunning || bar.controller.offlinePreviewRunning)
&& !bar.session.updatingFirmware
toolTipText: bar.device.deviceVerified ? "Stop Sampled Values output" : "Stop local preview only"
toolTipText: "Stop"
onClicked: {
if (!bar.device.deviceVerified) bar.controller.offlinePreviewRunning = false
else bar.workflow.requestStop()
}
}

Rectangle { width: 1; height: 24; color: bar.theme.lineSoft }
Rectangle { width: 1; height: 42; color: bar.theme.lineSoft }

RibbonAction {
theme: bar.theme
uiFont: bar.uiFont
text: bar.compact ? "SCL" : "Open SCL"
iconSource: Qt.resolvedUrl("../assets/material/folder_open.svg")
enabled: bar.session.engineeringEditable
enabled: bar.session.engineeringEditable && !bar.workflow.startQueued
toolTipText: "Open IEC 61850 engineering configuration"
onClicked: bar.workflow.openEngineeringDialog()
}
Expand All @@ -135,7 +133,7 @@ Rectangle {
uiFont: bar.uiFont
text: "4I+4V"
iconSource: Qt.resolvedUrl("../assets/material/dashboard.svg")
enabled: bar.session.engineeringEditable
enabled: bar.session.engineeringEditable && !bar.workflow.startQueued
toolTipText: "Use the built-in 4I + 4V / 4000 samples/s profile"
onClicked: bar.workflow.useBuiltInProfile()
}
Expand Down Expand Up @@ -194,19 +192,17 @@ Rectangle {
RibbonAction {
theme: bar.theme
uiFont: bar.uiFont
text: bar.compact ? "" : "Advanced"
iconOnly: bar.compact
text: "Advanced"
iconOnly: false
iconSource: Qt.resolvedUrl("../assets/material/settings.svg")
tone: "neutral"
toolTipText: bar.session.firmwareUpdateAvailable
? "Advanced controls · optional firmware update available"
: "Advanced device, firmware, waveform and timing controls"
toolTipText: "Advanced"
onClicked: bar.controller.openConfiguration()
}

Rectangle {
Layout.preferredWidth: bar.compact ? 92 : 112
Layout.preferredHeight: 30
Layout.preferredWidth: bar.compact ? 84 : 108
Layout.preferredHeight: 36
Layout.alignment: Qt.AlignVCenter
radius: 15
color: "#0a1016"
Expand Down
Loading
Loading