Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
4662538
feat(ptp-sv): port M1/M2/M3 clock phase, source-local AUTO and guarde…
masarray Oct 17, 2026
fc5d676
ci(firmware): build traceable SOURCE phase-servo + MAC evidence with …
masarray Oct 17, 2026
5c168c0
ci(studio): require exact-matched M3C.5 phase-evidence firmware in Wi…
masarray Oct 17, 2026
403a829
fix(studio): make pending setpoints visible and remove lab-only Advan…
masarray Oct 17, 2026
b23ec88
fix(studio): make pending setpoints visible and remove lab-only Advan…
masarray Oct 17, 2026
81999d9
fix(studio): make pending setpoints visible and remove lab-only Advan…
masarray Oct 17, 2026
180749e
ci(ptp): enforce shipped 1s IEC 61850-9-3 Sync profile instead of sta…
masarray Oct 17, 2026
f3194a0
ci(ptp): verify bounded SOURCE readiness freshness rather than obsole…
masarray Oct 17, 2026
cbc8181
ci(ptp): compile every newly ported regression target on MSVC before …
masarray Oct 17, 2026
65f3f0c
ci(sv): validate last-log-before-timer invariant for M3 epoch-aware S…
masarray Oct 17, 2026
fb4329e
fix(ptp-sv): qualify sample time not packet TX epoch; preserve UI PTP…
masarray Oct 17, 2026
3fb9969
fix(studio): keep exact SV setpoints and refine operator layout (#242)
masarray Oct 18, 2026
8999bac
fix(studio): flush phasors before START, responsive stacked matrices …
masarray Oct 18, 2026
aa1a167
fix(studio): readable 4I+4V stacked workspace and locked SV/PTP UI ac…
masarray Oct 18, 2026
0a3e916
fix(studio): readable operator UI and table phase integrity without P…
masarray Oct 18, 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
10 changes: 10 additions & 0 deletions .github/workflows/arstack-studio-native-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ jobs:
# rather than taking an older public firmware that lacks PROFILE-BINARY-V1.
firmware:
uses: ./.github/workflows/esp32p4-smv-injector.yml
with:
# Only the GUI Preview installer opts into measured local phase sync.
# Stable release workflow keeps ordinary firmware until board validation.
phase_sync_preview: true

windows-preview:
needs: firmware
Expand Down Expand Up @@ -95,6 +99,12 @@ jobs:
throw 'Matching firmware job did not provide its manifest'
}
$manifest = Get-Content -Raw -LiteralPath $manifestPath | ConvertFrom-Json
if ($manifest.phaseSyncPreview -ne $true -or
$manifest.phaseServoEnabled -ne $true -or
$manifest.canonicalMacTxEvidenceEnabled -ne $true -or
$manifest.broadcastMirrorEnabled -ne $false) {
throw 'Studio preview refuses old/default SMV firmware without measured PTP-SV phase and MAC TX evidence'
}
$expectedCapabilities = @('SMV-4I4V', 'PROFILE', 'PROFILE-BINARY-V1', 'LIVE-SETPOINTS', 'SESSION-LEASE', 'PTP-P2', 'SMPSYNCH-AUTO')
foreach ($capability in $expectedCapabilities) {
if ($manifest.capabilities -notcontains $capability) { throw "PR firmware lacks capability: $capability" }
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/esp32p4-ptp-lab.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ jobs:
'g_announce_sent.load',
'g_sync_sent.load',
'g_follow_up_sent.load',
'return g_ptp_ready.load',
'return source_has_fresh_evidence();',
]
combined = eth + '\n' + ptp
missing = [token for token in required if token not in combined]
Expand Down
52 changes: 48 additions & 4 deletions .github/workflows/esp32p4-smv-injector.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ name: ESP32-P4 SMV Injector

on:
workflow_call:
inputs:
phase_sync_preview:
description: "Build PTP-to-SV measured-local AUTO candidate only for Studio preview"
type: boolean
default: false
required: false
workflow_dispatch:
push:
branches: [main]
Expand Down Expand Up @@ -106,9 +112,36 @@ jobs:
echo "ARSTACK_FIRMWARE_BUILD_ID=${ARSTACK_SOURCE_COMMIT:0:16}" >> "$GITHUB_ENV"
echo "Exact firmware build identity pinned to ${ARSTACK_SOURCE_COMMIT:0:16}"

- name: Enable PTP-SV measured AUTO preview profile
if: ${{ inputs.phase_sync_preview == true }}
run: |
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
cfg = Path('sdkconfig.defaults')
content = cfg.read_text(encoding='utf-8')
original = 'CONFIG_AR_SMV_BROADCAST_MIRROR=y'
assert content.count(original) == 1, 'expected controlled mirror policy'
content = content.replace(original, '# CONFIG_AR_SMV_BROADCAST_MIRROR is not set')
content += ('\n# SOURCE PTP-SV AUTO phase qualification - preview only\n'
'CONFIG_AR_SMV_PTP_PHASE_SERVO_EXPERIMENTAL=y\n'
'CONFIG_AR_SMV_HW_TX_EVIDENCE_EXPERIMENTAL=y\n')
cfg.write_text(content, encoding='utf-8')
PY

- name: Select ESP32-P4 target
run: source /opt/esp/idf/export.sh && idf.py set-target esp32p4

- name: Verify phase-qualified Studio preview is the actual compiled profile
if: ${{ inputs.phase_sync_preview == true }}
run: |
set -euo pipefail
grep -qx 'CONFIG_AR_SMV_PTP_PHASE_SERVO_EXPERIMENTAL=y' sdkconfig
grep -qx 'CONFIG_AR_SMV_HW_TX_EVIDENCE_EXPERIMENTAL=y' sdkconfig
grep -qx '# CONFIG_AR_SMV_BROADCAST_MIRROR is not set' sdkconfig
grep -qx 'CONFIG_AR_PTP_OPERATING_ROLE=0' sdkconfig
echo 'SOURCE local AUTO preview: servo ON; canonical MAC TX evidence ON; mirror OFF'

- name: Verify P0 pre-v3 silicon policy
run: |
set -euo pipefail
Expand All @@ -124,7 +157,7 @@ jobs:
grep -qx 'CONFIG_AR_PTP_DOMAIN=0' sdkconfig
grep -qx 'CONFIG_AR_PTP_TRANSPORT_SPECIFIC=0' sdkconfig
grep -qx 'CONFIG_AR_PTP_RESPOND_PDELAY=y' sdkconfig
grep -qx 'CONFIG_AR_PTP_SYNC_INTERVAL_MS=250' sdkconfig
grep -qx 'CONFIG_AR_PTP_SYNC_INTERVAL_MS=1000' sdkconfig
grep -qx 'CONFIG_AR_PTP_ANNOUNCE_INTERVAL_MS=1000' sdkconfig
echo 'Production timing profile: PASS · serialized shared EMAC TX · PTP-P2 LAB_SOURCE · HW timestamp adapter compiled'

Expand All @@ -148,9 +181,9 @@ jobs:
start_begin = source.index('if (take_start_request())')
start_end = source.index('if (!live_tx_running())', start_begin)
start_block = source[start_begin:start_end]
clock_start = start_block.rindex(
'sample_clock = start_sample_clock(task_handle, active_profile.publisher_rate_hz);'
)
# M3 passes the ETH/PTP epoch context as additional arguments.
# Assert that the timer is still armed after the final console log.
clock_start = start_block.rindex('sample_clock = start_sample_clock(')
assert start_block.rindex('ESP_LOGI') < clock_start
assert 'ESP_LOG' not in start_block[clock_start:]
assert 'static_assert(std::atomic<std::uint32_t>::is_always_lock_free);' in source
Expand Down Expand Up @@ -194,7 +227,13 @@ jobs:
import os
import re
import sys
from pathlib import Path
image, sha256, source_commit = sys.argv[1:]
cfg = Path('sdkconfig').read_text(encoding='utf-8')
servo = 'CONFIG_AR_SMV_PTP_PHASE_SERVO_EXPERIMENTAL=y' in cfg
mac_evidence = 'CONFIG_AR_SMV_HW_TX_EVIDENCE_EXPERIMENTAL=y' in cfg
no_mirror = '# CONFIG_AR_SMV_BROADCAST_MIRROR is not set' in cfg
candidate = servo and mac_evidence and no_mirror
if not re.fullmatch(r"[0-9a-f]{40}", source_commit):
raise SystemExit("Firmware source SHA unavailable/invalid; refusing a zero-provenance package")
manifest = {
Expand All @@ -211,6 +250,11 @@ jobs:
"espIdf": "5.5.4",
"sourceCommit": source_commit,
"buildId": source_commit[:16],
"phaseSyncPreview": candidate,
"phaseServoEnabled": servo,
"canonicalMacTxEvidenceEnabled": mac_evidence,
"broadcastMirrorEnabled": not no_mirror,
"smpSynchQualifiedAtBuild": False,
}
with open("build/firmware-manifest.json", "w", encoding="utf-8", newline="\n") as handle:
json.dump(manifest, handle, indent=2, sort_keys=True)
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/ptp-core-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ jobs:
Build-Target 'smp_synch_wire_offset_tests'
Build-Target 'ptp_discipline_tests'
Build-Target 'ptp_p2_hardening_tests'
Build-Target 'ptp_power_profile_tests'
Build-Target 'sv_ptp_epoch_tests'
Build-Target 'ptp_control_cancellation_tests'
Build-Target 'ptp_emac_role_ownership_tests'
Build-Target 'ptp_canonical_authority_tests'
Build-Target 'ptp_receiver_clock_authority_tests'
Build-Target 'sv_ptp_clock_lease_tests'
Build-Target 'sv_ptp_phase_authority_tests'
Build-Target 'sv_tx_egress_evidence_tests'
Build-Target 'ptp_profile_analyze'

- name: Run PTP regression and analyzer smoke tests on MSVC
Expand Down
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ if(ARIEC61850_BUILD_TOOLS)
target_compile_features(ariec61850_ptp_analyze PRIVATE cxx_std_20)
ariec61850_apply_warnings(ariec61850_ptp_analyze)
ariec61850_apply_sanitizers(ariec61850_ptp_analyze)
add_executable(ariec61850_ptp_sv_wire_audit tools/ptp_sv_wire_audit.cpp)
target_link_libraries(ariec61850_ptp_sv_wire_audit PRIVATE ARIEC61850::core)
target_compile_features(ariec61850_ptp_sv_wire_audit PRIVATE cxx_std_20)
ariec61850_apply_warnings(ariec61850_ptp_sv_wire_audit)
ariec61850_apply_sanitizers(ariec61850_ptp_sv_wire_audit)

add_executable(ariec61850_comtrade_inspect tools/comtrade_inspect.cpp)
target_link_libraries(ariec61850_comtrade_inspect PRIVATE ARIEC61850::core)
Expand Down Expand Up @@ -498,6 +503,8 @@ if(ARIEC61850_BUILD_TESTS)
endforeach()

if(ARIEC61850_BUILD_TOOLS)
add_test(NAME ariec61850_ptp_sv_wire_audit_selftest
COMMAND ariec61850_ptp_sv_wire_audit --self-test)
add_test(
NAME ariec61850_pcap_interop_smoke
COMMAND ariec61850_pcap_interop_check
Expand Down
17 changes: 13 additions & 4 deletions apps/arstack_studio/qml/CalmButton.qml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Button {
property url iconSource
property real iconSize: 16
property string toolTipText: ""
readonly property string hoverPath: iconSource.toString().replace("/assets/material/", "/assets/material_dark/")

implicitHeight: theme ? theme.controlHeight : 36
implicitWidth: Math.max(64, contentItem.implicitWidth + 20)
Expand All @@ -22,29 +23,36 @@ Button {
Behavior on scale { NumberAnimation { duration: 70; easing.type: Easing.OutCubic } }

readonly property color activeFill: !enabled ? "#10161d" :
hovered ? "#edf5ff" :
tone === "success" ? (hovered ? "#1e5c43" : "#194d38") :
tone === "danger" ? (hovered ? "#523039" : theme.redSoft) :
tone === "accent" ? (hovered ? "#1d3c5e" : theme.accentSoft) :
(down ? "#1a2530" : hovered ? theme.raisedHover : "#141c25")
readonly property color activeEdge: !enabled ? theme.lineSoft :
hovered ? "#aac9e9" :
tone === "success" ? "#347a59" :
tone === "danger" ? "#86434b" :
tone === "accent" ? "#315f8d" :
(activeFocus ? theme.accent : hovered ? "#3a4a5b" : theme.line)
readonly property color activeText: !enabled ? theme.muted2 :
hovered ? "#142334" :
tone === "success" ? "#d6f4e6" :
tone === "danger" ? "#ffdce0" :
tone === "accent" ? "#dcebff" : theme.textSoft

contentItem: Row {
spacing: control.iconSource.toString().length > 0 ? 7 : 0
anchors.centerIn: parent
contentItem: Item {
implicitWidth: centeredContent.implicitWidth
implicitHeight: centeredContent.implicitHeight
Row {
id: centeredContent
anchors.centerIn: parent
spacing: control.iconSource.toString().length > 0 ? 7 : 0
Image {
visible: control.iconSource.toString().length > 0
width: control.iconSize
height: control.iconSize
anchors.verticalCenter: parent.verticalCenter
source: control.iconSource
source: control.hovered && control.enabled ? control.hoverPath : control.iconSource
sourceSize.width: control.iconSize * 2
sourceSize.height: control.iconSize * 2
opacity: control.enabled ? 0.94 : 0.30
Expand All @@ -56,6 +64,7 @@ Button {
anchors.verticalCenter: parent.verticalCenter
Behavior on color { ColorAnimation { duration: 90 } }
}
}
}

ToolTip.visible: hovered && toolTipText.length > 0
Expand Down
6 changes: 3 additions & 3 deletions apps/arstack_studio/qml/ConfigurationHub.qml
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ Item {
font.weight: checked ? Font.DemiBold : Font.Medium
contentItem: Label {
text: parent.text
color: parent.checked ? hub.theme.text : hub.theme.muted
color: parent.hovered ? "#142334" : parent.checked ? hub.theme.text : hub.theme.muted
font: parent.font
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
color: parent.checked ? hub.theme.raised : "transparent"
color: parent.hovered ? "#edf5ff" : parent.checked ? hub.theme.raised : "transparent"
radius: 6
border.width: parent.checked ? 1 : 0
border.color: hub.theme.line
Expand Down Expand Up @@ -79,7 +79,7 @@ Item {
font.weight: Font.DemiBold
}
Label {
text: "Lab simulation controls · configure one task at a time"
text: "Signals, firmware, PTP and device settings"
color: hub.theme.muted
font.family: hub.uiFont
font.pixelSize: 11
Expand Down
4 changes: 2 additions & 2 deletions apps/arstack_studio/qml/DarkToolButton.qml
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ ToolButton {
visible: control.iconSource.toString().length === 0
anchors.fill: parent
text: control.text
color: control.enabled ? (control.hovered ? "#ffffff" : theme.textSoft) : theme.muted2
color: control.enabled ? (control.hovered ? "#142334" : theme.textSoft) : theme.muted2
font: control.font
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
background: Rectangle {
radius: 6
color: control.down ? theme.accentSoft : (control.hovered ? theme.raisedHover : "transparent")
color: control.down ? "#d6e6f9" : (control.hovered ? "#edf5ff" : "transparent")
border.width: control.activeFocus ? 1 : 0
border.color: theme.accent
}
Expand Down
Loading
Loading