Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ba0d31f
Add native Qt ARStack Studio build scaffold
masarray Aug 11, 2026
f2a10bb
Add Qt SCL profile model interface
masarray Aug 11, 2026
f05116d
Implement direct C++ SCL compilation for Qt
masarray Aug 11, 2026
73795a1
Add Qt application entry point
masarray Aug 11, 2026
25fb843
Add premium Qt Quick SMV injection workspace
masarray Aug 11, 2026
ec547ff
Simplify native Qt premium injection workspace
masarray Aug 11, 2026
36204cf
Align Qt build scaffold with Qt 6 CMake guidance
masarray Aug 11, 2026
98fef69
Share ESP32-P4 SV profile support classification
masarray Aug 11, 2026
32be3e3
Add native Qt serial device controller
masarray Aug 11, 2026
ec3ee6b
Implement native Qt serial SMV control transport
masarray Aug 11, 2026
720d1af
Use shared ESP32 profile gate in Qt SCL model
masarray Aug 11, 2026
dada745
Link native Qt serial transport
masarray Aug 11, 2026
681d8f3
Expose native serial controller to QML
masarray Aug 11, 2026
f581827
Wire latest SMV profile and live controls into Qt UX
masarray Aug 11, 2026
ca6b9e9
Add native Qt Studio build and launch smoke CI
masarray Aug 11, 2026
22ab078
Reuse shared ESP32 profile support gate in inspector
masarray Aug 11, 2026
24d3273
Retain native Qt build logs on CI failures
masarray Aug 11, 2026
ceca8cd
Fix Qt QML syntax and streamline native injector workspace
masarray Aug 11, 2026
fd64c67
Document native Qt SMV Injector scope and workflow
masarray Aug 11, 2026
b2d2337
Allow Qt QML wrapper for SCL profile model
masarray Aug 11, 2026
387308e
Allow Qt QML wrapper for native device controller
masarray Aug 11, 2026
a77758a
Polish Qt injector P0 live workflow
masarray Aug 11, 2026
b3940d9
Fix Qt P0 state badge color
masarray Aug 11, 2026
655d9d5
Polish Qt SMV Injector P1 UX
masarray Aug 11, 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
80 changes: 80 additions & 0 deletions .github/workflows/arstack-studio-qt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: ARStack Studio Qt

on:
push:
paths:
- 'apps/arstack_studio/**'
- 'include/ariec61850/sampled_values/**'
- 'src/scl/**'
- 'include/ariec61850/scl/**'
- '.github/workflows/arstack-studio-qt.yml'
pull_request:
paths:
- 'apps/arstack_studio/**'
- 'include/ariec61850/sampled_values/**'
- 'src/scl/**'
- 'include/ariec61850/scl/**'
- '.github/workflows/arstack-studio-qt.yml'

permissions:
contents: read

jobs:
qt-build-smoke:
runs-on: ubuntu-latest
container: ubuntu:26.04
steps:
- name: Install build and Qt dependencies
shell: bash
run: |
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates git cmake ninja-build g++ \
qt6-base-dev qt6-declarative-dev qt6-serialport-dev \
qml6-module-qtquick qml6-module-qtquick-controls \
qml6-module-qtquick-layouts qml6-module-qtquick-dialogs \
qml6-module-qtquick-window

- name: Checkout
uses: actions/checkout@v4

- name: Configure
shell: bash
run: |
set -o pipefail
cmake -S apps/arstack_studio -B build-arstack-studio -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DARIEC61850_WARNINGS_AS_ERRORS=ON 2>&1 | tee /tmp/arstack-studio-configure.log

- name: Build
shell: bash
run: |
set -o pipefail
cmake --build build-arstack-studio --target arstack_studio --parallel 2 \
2>&1 | tee /tmp/arstack-studio-build.log

- name: Headless QML launch smoke
shell: bash
run: |
set +e
timeout 6s env QT_QPA_PLATFORM=offscreen QT_QUICK_BACKEND=software \
./build-arstack-studio/arstack_studio > /tmp/arstack-studio-launch.log 2>&1
status=$?
set -e
cat /tmp/arstack-studio-launch.log
if [ "$status" -ne 124 ]; then
echo "ARStack Studio exited before smoke timeout (status=$status)."
exit "$status"
fi

- name: Retain Qt smoke logs
if: always()
uses: actions/upload-artifact@v4
with:
name: arstack-studio-qt-logs
path: |
/tmp/arstack-studio-configure.log
/tmp/arstack-studio-build.log
/tmp/arstack-studio-launch.log
if-no-files-found: ignore
retention-days: 7
59 changes: 59 additions & 0 deletions apps/arstack_studio/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
cmake_minimum_required(VERSION 3.24)

project(ARStackStudio VERSION 0.1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

find_package(Qt6 6.5 REQUIRED COMPONENTS Core Gui Qml Quick QuickControls2 SerialPort)
qt_standard_project_setup(REQUIRES 6.5)

# Reuse the repository's IEC 61850/SCL implementation directly. The desktop UI
# is a presentation/control plane; it must not grow a second SCL semantic engine.
set(ARIEC61850_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(ARIEC61850_BUILD_TOOLS OFF CACHE BOOL "" FORCE)
set(ARIEC61850_BUILD_FUZZERS OFF CACHE BOOL "" FORCE)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../.. ${CMAKE_BINARY_DIR}/ariec61850-core EXCLUDE_FROM_ALL)

qt_add_executable(arstack_studio
src/main.cpp
src/SclProfileModel.cpp
src/SclProfileModel.hpp
src/DeviceController.cpp
src/DeviceController.hpp
)

qt_add_qml_module(arstack_studio
URI ARStack.Studio
VERSION 1.0
QML_FILES
qml/Main.qml
qml/StudioTheme.qml
qml/SurfacePanel.qml
qml/CalmButton.qml
qml/NumericField.qml
qml/StateBadge.qml
qml/ProfileInspector.qml
qml/SignalMatrix.qml
qml/QualityEditor.qml
qml/SignalPreview.qml
qml/StatusToast.qml
)

target_link_libraries(arstack_studio PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Qml
Qt6::Quick
Qt6::QuickControls2
Qt6::SerialPort
ARIEC61850::core
)

target_compile_features(arstack_studio PRIVATE cxx_std_20)

if(WIN32)
set_target_properties(arstack_studio PROPERTIES WIN32_EXECUTABLE TRUE)
endif()
105 changes: 105 additions & 0 deletions apps/arstack_studio/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# ARStack Studio — native Qt SMV Injector

`apps/arstack_studio` is the native Qt 6 / C++ / QML operator surface for the ESP32-P4 Sampled Values injector.

This branch is intentionally stacked on `feature/smv-scl-profile-bridge` so the desktop application follows the latest SCL/profile and embedded-runtime behavior instead of freezing an older web-GUI snapshot.

## Architecture

```text
SCL / CID / SCD / IID
-> ARIEC61850 SclParser
-> SvPublisherProfileCompiler
-> shared ESP32-P4 deployment support classifier
-> Qt SclProfileModel
-> operator selects / validates stream
-> Qt DeviceController (QSerialPort)
-> current ESP32-P4 PROFILE + live-control protocol
-> deterministic publisher on ESP32-P4
```

The Qt application is a presentation and control plane. The PC is not the 4000/4800 fps realtime clock and does not replace the ESP32-P4 publisher.

## Latest workflow carried into Qt

- direct native SCL/CID/SCD/IID import using the repository C++ parser;
- resolved SampledValueControl stream selection;
- compatibility Class A / B / C;
- fail-closed ESP32-P4 device-support classification;
- explicit sample-counter-modulus confirmation for Class-B candidate profiles;
- explicit current counts/A and voltage counts/V test scaling;
- immutable profile deployment while STOPPED;
- profile identity: svID, data set, destination MAC, APPID, VLAN/PCP, confRev, publisher rate and counter modulus;
- optional DataSet / SampleRate ASDU-field flags already supported by the current device bridge;
- native serial 115200 8N1 control using Qt SerialPort;
- live magnitude, phase, frequency, enable and quality controls;
- START / STOP / ZERO / SHOW and runtime telemetry;
- keyboard-first 4I + 4V matrix navigation;
- 3-phase linkage;
- persistent generated phasor and waveform preview;
- serial diagnostics surface.

The current deployment gate intentionally accepts only the embedded layout already supported by the ESP32-P4 runtime. Broader SCL structures may parse correctly while remaining `unsupported-layout`; the GUI does not guess them into the firmware profile.

## Truthfulness boundary

The phasor and waveform are **generated setpoint previews**. They visualize the configured engineering state. They are not independent Ethernet on-wire evidence.

A later Process Bus monitor can add an observed trace and generated-vs-observed comparison without changing this distinction.

`smpSynch` remains an embedded timing/synchronization truth. The GUI must not claim synchronized output until the runtime has measured synchronization evidence.

## Build

Qt 6.5+ is required with:

- Qt Core / Gui / Qml / Quick / QuickControls2
- Qt SerialPort
- a C++20 compiler
- CMake 3.24+

### Windows example

From the repository root with a Qt-enabled developer shell:

```powershell
cmake -S apps/arstack_studio -B build-arstack-studio -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build-arstack-studio --target arstack_studio --parallel
.\build-arstack-studio\arstack_studio.exe
```

For redistribution, package the required dynamically linked Qt runtime libraries with the normal Qt deployment tooling and review the applicable Qt/LGPL distribution obligations for the chosen module set.

## CI

`.github/workflows/arstack-studio-qt.yml` performs:

1. standalone Qt configure;
2. native C++ / QML build;
3. headless QML launch smoke test;
4. retained configure/build/launch logs on every run.

A green desktop CI proves that the application builds and QML instantiates in the hosted environment. It does **not** replace physical ESP32-P4 / Ethernet validation.

## Physical acceptance still required

Before this desktop GUI is treated as a proven replacement for the current bench surface, retain hardware evidence for at least:

1. native serial connect and SHOW/PROFILE SHOW;
2. development 4000 fps START/STOP and live SET/FREQ/ENABLE/QUALITY updates;
3. SCL Class-A profile deployment followed by PROFILE committed/armed confirmation;
4. 4800 fps profile deployment and observed ~4800 fps with the expected smpCnt cycle;
5. live magnitude/phase changes without unintended counter restart;
6. generated-vs-on-wire identity comparison using a trusted capture path.

## UI direction

The desktop visual target is a calm premium engineering instrument:

- Current and Voltage are the primary working matrices;
- SCL/profile information stays available without dominating the work surface;
- phasor and waveform remain persistent;
- Start/Stop remain fixed and obvious;
- values are optimized for keyboard operation;
- no browser/local-web-server dependency is required by the desktop application;
- visualization remains intentionally lightweight before any future custom `QQuickItem`/scene-graph optimization.
46 changes: 46 additions & 0 deletions apps/arstack_studio/qml/CalmButton.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Controls

Button {
id: control
property var theme
property string uiFont: "Inter"
property string tone: "neutral"

implicitHeight: 34
implicitWidth: Math.max(70, contentItem.implicitWidth + 24)
font.family: uiFont
font.pixelSize: 10
font.weight: Font.DemiBold

readonly property color activeFill: !enabled ? "#121820" :
tone === "success" ? "#194d38" :
tone === "danger" ? theme.redSoft :
tone === "accent" ? theme.accentSoft :
(down ? "#1c2733" : hovered ? theme.raisedHover : theme.raised)
readonly property color activeEdge: !enabled ? theme.lineSoft :
tone === "success" ? "#347a59" :
tone === "danger" ? "#86434b" :
tone === "accent" ? "#315f8d" :
(hovered ? "#3a4a5b" : theme.line)
readonly property color activeText: !enabled ? theme.muted2 :
tone === "success" ? "#d6f4e6" :
tone === "danger" ? "#ffdce0" : theme.textSoft

contentItem: Text {
text: control.text
color: control.activeText
font: control.font
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}

background: Rectangle {
radius: 6
color: control.activeFill
border.width: 1
border.color: control.activeEdge
Behavior on color { ColorAnimation { duration: 90 } }
}
}
Loading
Loading