Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
170 commits
Select commit Hold shift + click to select a range
cd256bc
chore: stage PTP production integration automation
masarray Sep 15, 2026
9cbe678
chore: split PTP source integration from workflow updates
masarray Sep 15, 2026
63d050f
PTP: promote hardware timing engine into v0.1.1 production firmware
github-actions[bot] Sep 15, 2026
b39931f
ci: make PTP-P2 part of production SMV firmware contract
masarray Sep 15, 2026
b68795d
release: advance ARStack Studio package contract to v0.1.1
masarray Sep 15, 2026
10a0ed9
chore: remove PTP production staging helper
masarray Sep 15, 2026
3b3a5b0
chore: stage v0.1.1 manifest capability hardening
masarray Sep 15, 2026
4b739ff
PTP: fail closed on incomplete v0.1.1 firmware capabilities
github-actions[bot] Sep 15, 2026
082e2b1
chore: remove v0.1.1 manifest staging helper
masarray Sep 15, 2026
5535b14
chore: stage PTP VLAN-off production compile fix
masarray Sep 15, 2026
0c581cb
PTP: support untagged production profile without VLAN Kconfig macros
github-actions[bot] Sep 15, 2026
53607af
chore: remove PTP VLAN-off staging helper
masarray Sep 15, 2026
efbba0b
chore: stage v0.1.1 lifecycle and PTP stabilization
masarray Sep 15, 2026
0199df5
chore: simplify v0.1.1 stabilization staging
masarray Sep 15, 2026
232474b
fix(studio): retire workers before GUI event loop exits
github-actions[bot] Sep 15, 2026
4d9a477
chore: stage verified PTP source and normal-close fixes
masarray Sep 15, 2026
8e2f3e0
fix(v0.1.1): verify PTP source TX and normal GUI shutdown
github-actions[bot] Sep 15, 2026
822c47e
ux(ptp): make source start obvious and evidence-driven
masarray Sep 15, 2026
a2e81a8
ci(studio): gate normal Windows close and immediate reopen
masarray Sep 15, 2026
25f0d97
chore: stage real main-window close regression
masarray Sep 15, 2026
a6dc074
ci(ptp): lock verified source-start contract
masarray Sep 15, 2026
ba9899d
test(studio): exercise real primary-window close path
github-actions[bot] Sep 15, 2026
fccf017
chore: stage role-correct PTP status text
masarray Sep 15, 2026
4d98df2
fix(ptp): keep role-specific start semantics truthful
github-actions[bot] Sep 15, 2026
41bd10f
chore(ci): remove temporary v0.1.1 staging workflow
masarray Sep 15, 2026
b1d5003
fix(ptp): use verified TX readiness on ESP-IDF 5.5
masarray Sep 15, 2026
685e9c7
chore(ci): stage v0.1.1 harness fixture fix
masarray Sep 15, 2026
fe35964
test(studio): align S8 fixture with v0.1.1 capabilities
github-actions[bot] Sep 15, 2026
10cfc12
ci(ptp): gate verified TX readiness on ESP-IDF 5.5
masarray Sep 15, 2026
9f286ed
chore(ci): remove temporary v0.1.1 harness patch workflow
masarray Sep 15, 2026
c6f1c59
chore(ci): stage PTP stop declaration fix
masarray Sep 15, 2026
8530d66
chore(ci): remove temporary PTP declaration helper
masarray Sep 15, 2026
17036bb
fix(ptp): declare bounded source stop helper
masarray Sep 15, 2026
b6da439
build(ptp): inject internal stop declaration for source runtime
masarray Sep 15, 2026
7e7768c
fix(ptp): declare stop helper before bounded start
github-actions[bot] Sep 15, 2026
0b3a85a
build(ptp): keep lifecycle declaration local to source
masarray Sep 16, 2026
566afaf
chore(ptp): remove temporary forward-declaration shim
masarray Sep 16, 2026
cc15cc9
ci: stage P0 shared Ethernet TX recovery
masarray Sep 16, 2026
20c3e68
ci: stage authoritative P0 SMV/PTP recovery
masarray Sep 16, 2026
f022b34
fix(p0): serialize concurrent SMV and PTP Ethernet TX
masarray Sep 16, 2026
5d6990f
fix(p0): fail closed when canonical SV transport is dead
masarray Sep 16, 2026
0100563
fix(p0): restore EMAC after failed PTP source start
masarray Sep 16, 2026
094fb86
chore: remove P0 Ethernet recovery staging helper
masarray Sep 16, 2026
b3f6698
ci: rerun authoritative P0 recovery on current head
masarray Sep 16, 2026
7026ad5
chore: remove temporary P0 authoritative recovery workflow
masarray Sep 16, 2026
9e22a6a
ci: stage authoritative SMV/PTP P0 repair
masarray Sep 16, 2026
26d57a2
ci: move P0 repair logic into standalone script
masarray Sep 16, 2026
cada9aa
ci: run P0 repair script with minimal workflow
masarray Sep 16, 2026
fb6a402
ci: push P0 source repair separately from workflow edits
masarray Sep 16, 2026
326d215
ci: align PTP readiness gate with rollback start path
masarray Sep 16, 2026
91d6486
ci: lock shared EMAC and fail-closed SMV contract
masarray Sep 16, 2026
2fee41d
ci: exercise Studio against v0.1.1 production firmware contract
masarray Sep 16, 2026
6875cfa
ci: isolate P0 firmware source repair
masarray Sep 16, 2026
36b0e35
ci: run source-only P0 firmware repair from latest head
masarray Sep 16, 2026
f5a9739
ci: execute P0 source repair on Windows runner
masarray Sep 16, 2026
ec757f8
fix(p0): fail closed SMV transport and restore shared EMAC state
github-actions[bot] Sep 16, 2026
6b387df
ci: execute P0 source repair on ARM64 runner
masarray Sep 16, 2026
722122f
chore: remove temporary P0 repair workflow
masarray Sep 16, 2026
23d2864
chore: remove temporary Windows source repair workflow
masarray Sep 16, 2026
f6ad55c
chore: remove temporary ARM64 source repair workflow
masarray Sep 16, 2026
330e08f
chore: remove obsolete P0 repair script
masarray Sep 16, 2026
3d85fe6
chore: remove temporary P0 source repair script
masarray Sep 16, 2026
74ff9ee
ci: apply smart legacy firmware recovery fix
masarray Sep 16, 2026
206eb8a
ci: add smart firmware recovery patch helper
masarray Sep 16, 2026
0c4515d
ci: simplify smart firmware recovery repair runner
masarray Sep 16, 2026
1a2c6de
ci: run smart firmware recovery repair on Windows
masarray Sep 16, 2026
40a33f7
fix(studio): auto-recover trusted legacy ESP32-P4 firmware
github-actions[bot] Sep 16, 2026
5d930fe
chore: remove temporary smart recovery patch helper
masarray Sep 16, 2026
eea95d6
chore: remove temporary smart recovery workflow
masarray Sep 16, 2026
f05db91
ci: build smart recovery Windows candidate
masarray Sep 16, 2026
3c9e852
ci: route smart recovery candidate to Windows 2025
masarray Sep 16, 2026
d9334b7
ci: build smart recovery candidate with VS2026
masarray Sep 16, 2026
cc5c47e
ci: invoke NSIS from installed absolute path
masarray Sep 16, 2026
fe4661b
chore: remove focused smart recovery candidate workflow
masarray Sep 16, 2026
4cbde22
ci: apply physical P0 runtime repair
masarray Sep 16, 2026
f29bcb0
ci: stage P0 runtime repair script
masarray Sep 16, 2026
efc5426
ci: simplify P0 runtime repair runner
masarray Sep 16, 2026
2e6e5db
ci: move P0 repair to Ubuntu 22 runner
masarray Sep 16, 2026
15fe7fe
fix(p0): hard-exit GUI close and infer single-board firmware recovery
masarray Sep 16, 2026
63561ea
ci: run P0 runtime repair on Windows 2025
masarray Sep 16, 2026
0c75e46
ci: run P0 repair on macOS runner
masarray Sep 16, 2026
7d270ac
ci: build physical P0 Windows candidate
masarray Sep 16, 2026
1d92114
chore: remove temporary P0 runtime repair script
masarray Sep 16, 2026
00201cf
chore: remove temporary P0 runtime candidate workflow
masarray Sep 16, 2026
bee1a58
ci: stage CH343 fast discovery repair
masarray Sep 16, 2026
5744e74
ci: run CH343 fast discovery repair
masarray Sep 16, 2026
df3842f
fix(studio): prioritize CH343 USB injector discovery
masarray Sep 16, 2026
991eca5
ci: build CH343 fast discovery Windows candidate
masarray Sep 16, 2026
f70bc23
ci: fix CH343 candidate lifecycle gate
masarray Sep 16, 2026
2d4a0ba
ci: remove CH343 repair helper
masarray Sep 16, 2026
44f600d
ci: remove CH343 repair workflow
masarray Sep 16, 2026
402451d
ci: remove CH343 candidate workflow
masarray Sep 16, 2026
4616f98
ui: add modern ribbon action component
masarray Sep 16, 2026
fe39de1
ui: add ribbon group component
masarray Sep 16, 2026
5e69ffc
ui: add QML-native tabbed ribbon shell
masarray Sep 16, 2026
d31a65f
ci: add phase 1 ribbon migration patcher
masarray Sep 16, 2026
cc0a4de
ci: apply phase 1 ribbon migration
masarray Sep 16, 2026
17ebf92
ci: add P0 PTP varargs repair patcher
masarray Sep 16, 2026
b268636
ci: apply P0 PTP varargs correction
masarray Sep 16, 2026
d9503ef
fix(ptp): honor ESP-IDF 5.5 varargs contract and expose TX failures
Sep 16, 2026
54f3f32
ci: move ribbon phase 1 apply to macOS runner
masarray Sep 16, 2026
53c2e19
ci: lock ESP-IDF 5.5 PTP timestamp varargs contract
masarray Sep 16, 2026
0b2f9c5
ui: adopt modern tabbed ribbon and compact instrument shell
Sep 16, 2026
d5b5dfb
ci: add Windows fallback for ribbon phase 1 apply
masarray Sep 16, 2026
e1777ae
ci(p0): add ESP-IDF 5.5.4 macOS fallback build gate
masarray Sep 16, 2026
688e671
ci(p0): bound and serialize ESP32-P4 firmware builds
masarray Sep 16, 2026
f84dff6
ci(p0): bound and serialize ESP32-P4 PTP builds
masarray Sep 16, 2026
bc18996
ci: apply P0 firmware build identity contract
masarray Sep 16, 2026
51c954f
ci: stage P0 firmware build identity repair script
masarray Sep 16, 2026
f3bda8a
ci: run one-shot P0 build identity repair
masarray Sep 16, 2026
7c3f5b8
ci: allow one-shot P0 repair on PR runner
masarray Sep 16, 2026
b48e56a
ci: harden one-shot build identity repair
masarray Sep 16, 2026
5ffa3f3
ci: apply P0 build identity without workflow mutation
masarray Sep 16, 2026
09eced1
ci: make P0 build identity repair source-only
masarray Sep 16, 2026
501bd42
ci: move one-shot P0 repair to macOS runner
masarray Sep 16, 2026
6b81cb4
fix(p0): bind firmware updates to semantic build identity
Sep 16, 2026
6774f74
chore(p0): remove one-shot firmware repair scaffolding
masarray Sep 16, 2026
8558857
chore(ptp): retire one-shot varargs repair scaffolding
masarray Sep 16, 2026
decd9cd
test(studio): include firmware provenance in bundle fixture
masarray Sep 16, 2026
368e5cc
chore(ci): restore canonical Studio workflow before provenance fixtur…
masarray Sep 16, 2026
b2f9f5d
test(studio): include firmware provenance in bundle fixture
masarray Sep 16, 2026
b9e5f08
fix(firmware): fail closed semantic build provenance
masarray Sep 16, 2026
bc7d416
chore(p0): stage firmware advisory state-machine repair
masarray Sep 16, 2026
6ae0b1a
chore(p0): run firmware advisory repair once
masarray Sep 16, 2026
2015884
fix(studio): make firmware updates advisory and recoverable
Sep 16, 2026
1188a1b
chore(p0): restore canonical Windows lifecycle gate
masarray Sep 16, 2026
badc8c7
chore(p0): remove one-shot firmware advisory patcher
masarray Sep 16, 2026
f3305c5
fix(studio): make firmware flash progress stream-safe
masarray Sep 16, 2026
e680c60
fix(studio): parse espflash progress across output chunks
masarray Sep 16, 2026
d7ed5a5
feat(studio): present firmware update as staged instrument workflow
masarray Sep 16, 2026
1ec0fc8
chore(p0): add one-shot firmware progress finalizer
masarray Sep 16, 2026
46a2543
chore(p0): add one-shot firmware progress repair job
masarray Sep 16, 2026
8fd76f3
fix(studio): finalize firmware progress observability
Sep 16, 2026
6f572fb
chore(p0): complete firmware progress regression fixture
masarray Sep 16, 2026
d1ed7a4
chore(p0): complete regression fixture in finalizer
masarray Sep 16, 2026
ebfa398
fix(studio): finalize firmware progress regressions
Sep 16, 2026
f98d963
chore(p0): remove one-shot firmware progress helper
masarray Sep 16, 2026
bf20f1a
chore(p0): remove one-shot firmware progress patcher
masarray Sep 16, 2026
52eb18c
chore(p0): remove one-shot firmware progress workflow
masarray Sep 16, 2026
6944984
chore(p0): add one-shot duplicate stage cleanup
masarray Sep 16, 2026
283c395
chore(p0): add one-shot duplicate stage repair job
masarray Sep 16, 2026
37a3ca6
fix(studio): keep one supervisor update stage enum
Sep 16, 2026
3832a2c
chore(p0): remove duplicate-stage one-shot patcher
masarray Sep 16, 2026
1189ffb
chore(p0): remove duplicate-stage one-shot workflow
masarray Sep 16, 2026
1f4f130
ui(studio): simplify command bar actions
masarray Sep 17, 2026
d0166fd
ui(studio): replace tab ribbon with compact command bar
masarray Sep 17, 2026
9c4e78a
ui(studio): refine numeric field density and focus treatment
masarray Sep 17, 2026
936f321
ui(studio): align signal matrix and reduce visual noise
masarray Sep 17, 2026
b78e523
ci(ui): stage one-shot demo shell refinement
masarray Sep 17, 2026
2d2eccd
ci(ui): run one-shot demo shell refinement
masarray Sep 17, 2026
a9f6299
ui(studio): consolidate shell for demo-grade layout
Sep 17, 2026
b34d74d
chore(ui): remove one-shot demo shell patcher
masarray Sep 17, 2026
7380a62
chore(ui): remove one-shot demo shell workflow
masarray Sep 17, 2026
0fc9339
feat(studio): replace ribbon with premium single command bar
masarray Sep 17, 2026
6ff726a
ci(studio): stage phase 1A premium shell patch
masarray Sep 17, 2026
6cde21e
ci(studio): apply phase 1A premium shell patch
masarray Sep 17, 2026
670c4b5
feat(studio): simplify premium demo shell
Sep 17, 2026
63a50e2
chore(studio): remove phase 1A patch scaffold
masarray Sep 17, 2026
014bffe
chore(studio): remove phase 1A workflow scaffold
masarray Sep 17, 2026
300b514
ci(studio): stage phase 1B workspace polish
masarray Sep 17, 2026
7cd3b52
ci(studio): apply phase 1B workspace polish
masarray Sep 17, 2026
1c0d5de
feat(studio): polish injection workspace for demo
Sep 17, 2026
718169e
chore(studio): remove phase 1B patch scaffold
masarray Sep 17, 2026
16365e8
chore(studio): remove phase 1B workflow scaffold
masarray Sep 17, 2026
e393738
feat(studio): tighten premium demo density tokens
masarray Sep 17, 2026
cdda632
ui(studio): refine premium command bar hierarchy
masarray Sep 17, 2026
93b9204
ui(studio): tighten command action geometry
masarray Sep 17, 2026
692925c
ui(studio): reduce preview dock chrome
masarray Sep 17, 2026
f6981ae
ui(studio): refine signal matrix density and alignment
masarray Sep 17, 2026
bad4041
ui(studio): reduce quality strip visual noise
masarray Sep 17, 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
137 changes: 137 additions & 0 deletions .github/scripts/p0_ribbon_phase1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
from pathlib import Path
import re

ROOT = Path('.')


def replace_once(text: str, old: str, new: str, label: str) -> str:
if old not in text:
raise SystemExit(f'{label}: expected source block not found')
return text.replace(old, new, 1)

# Register QML components.
cmake_path = ROOT / 'apps/arstack_studio/CMakeLists.txt'
cmake = cmake_path.read_text(encoding='utf-8')
needle = ' qml/WorkflowBar.qml\n qml/CalmButton.qml\n'
replacement = (' qml/WorkflowBar.qml\n'
' qml/ModernRibbon.qml\n'
' qml/RibbonGroup.qml\n'
' qml/RibbonAction.qml\n'
' qml/CalmButton.qml\n')
cmake = replace_once(cmake, needle, replacement, 'CMake ribbon registration')
cmake_path.write_text(cmake, encoding='utf-8')

# Theme: define every surface token used by QML. This fixes the white PTP card.
theme_path = ROOT / 'apps/arstack_studio/qml/StudioTheme.qml'
theme = theme_path.read_text(encoding='utf-8')
theme = replace_once(theme,
' readonly property color surface2: "#0e141b"\n readonly property color raised: "#151d27"\n',
' readonly property color surface2: "#0e141b"\n readonly property color panelAlt: "#101820"\n readonly property color raised: "#151d27"\n',
'StudioTheme panelAlt')
theme_path.write_text(theme, encoding='utf-8')

# WorkflowBar keeps the existing session/dialog implementation, but presentation becomes ribbon-native.
workflow_path = ROOT / 'apps/arstack_studio/qml/WorkflowBar.qml'
workflow = workflow_path.read_text(encoding='utf-8')
anchor = ''' function resetDockLayout() {
controller.phasorDetached = false
controller.waveformDetached = false
controller.phasorDockVisible = true
controller.waveformDockVisible = false
controller.telemetryDockVisible = true
controller.telemetryExpanded = false
controller.showMessage("Dock layout reset.", false)
}
'''
helpers = anchor + '''
function openUpdatePrompt() {
updatePromptDeferred = false
updateDialog.open()
}

function openInstallPrompt() {
installPromptDeferred = false
installDialog.open()
}

function retryProfileSync() {
if (smartSession.retryProfileSync())
controller.showMessage("Retrying 4I+4V profile synchronization.", false)
else
controller.showMessage(smartSession.statusText, true)
}
'''
workflow = replace_once(workflow, anchor, helpers, 'WorkflowBar ribbon helpers')
pattern = re.compile(r'\n implicitHeight: 72\n color: theme\.surface2\n border\.color: theme\.line\n.*\n\}', re.S)
modern = '''
implicitHeight: ribbon.compact ? 70 : 96
color: "transparent"
border.width: 0

ModernRibbon {
anchors.fill: parent
theme: ribbon.theme
controller: ribbon.controller
workflow: ribbon
session: smartSession
device: ribbon.device
uiFont: ribbon.uiFont
compact: ribbon.compact
}
}'''
workflow, count = pattern.subn(modern, workflow, count=1)
if count != 1:
raise SystemExit(f'WorkflowBar presentation replacement count={count}')
workflow_path.write_text(workflow, encoding='utf-8')

# Main shell: denser desktop instrument proportions, transient-only toast, and 0 Hz = DC.
main_path = ROOT / 'apps/arstack_studio/qml/Main.qml'
main = main_path.read_text(encoding='utf-8')
main = replace_once(main,
' width: 1480\n height: 900\n minimumWidth: 1080\n minimumHeight: 720\n',
' width: 1240\n height: 760\n minimumWidth: 960\n minimumHeight: 620\n',
'Main default geometry')
main = replace_once(main,
' readonly property bool compactLayout: width < 1300\n',
' readonly property bool compactLayout: width < 1120\n',
'Main compact breakpoint')
main = replace_once(main,
' readonly property string toastMessage: transientMessage.length ? transientMessage : device.lastError\n readonly property bool toastError: transientMessage.length ? transientError : device.lastError.length > 0\n',
' readonly property string toastMessage: transientMessage\n readonly property bool toastError: transientError\n',
'Main transient toast policy')
main = replace_once(main,
' Layout.preferredHeight: 72\n Layout.minimumHeight: 72\n',
' Layout.preferredHeight: workflowBar.implicitHeight\n Layout.minimumHeight: workflowBar.implicitHeight\n',
'Main ribbon height')
main = replace_once(main,
' CalmButton { theme: studioTheme; uiFont: root.uiFont; text: "AC"; tone: root.signalFrequency > 0 ? "accent" : "normal"; implicitWidth: 46; onClicked: root.setWaveformMode("AC") }\n CalmButton { theme: studioTheme; uiFont: root.uiFont; text: "DC"; tone: root.signalFrequency === 0 ? "accent" : "normal"; implicitWidth: 46; onClicked: root.setWaveformMode("DC") }\n Rectangle { width: 1; height: 24; color: studioTheme.lineSoft }\n\n',
'',
'Remove AC/DC mode buttons')
main = replace_once(main,
' enabled: root.signalFrequency > 0\n validator: DoubleValidator { bottom: 0.001; top: 1000.0; decimals: 3 }\n',
' enabled: true\n validator: DoubleValidator { bottom: 0.0; top: 1000.0; decimals: 3 }\n',
'Frequency accepts DC zero')
main = main.replace('root.showMessage("AC frequency must be greater than 0 and not exceed 1000 Hz.", true)',
'root.showMessage("Frequency must be within 0..1000 Hz (0 = DC).", true)')
main = main.replace('CalmButton { visible: root.signalFrequency > 0; theme: studioTheme; uiFont: root.uiFont; text: "50";',
'CalmButton { theme: studioTheme; uiFont: root.uiFont; text: "50";', 1)
main = main.replace('CalmButton { visible: root.signalFrequency > 0; theme: studioTheme; uiFont: root.uiFont; text: "60";',
'CalmButton { theme: studioTheme; uiFont: root.uiFont; text: "60";', 1)
quick_50 = ' CalmButton { theme: studioTheme; uiFont: root.uiFont; text: "50"; implicitWidth: 44; onClicked: root.setFrequencyValue(50) }\n'
if quick_50 not in main:
raise SystemExit('Quick frequency 50 button not found')
main = main.replace(quick_50,
' CalmButton { theme: studioTheme; uiFont: root.uiFont; text: "0"; implicitWidth: 44; toolTipText: "DC"; onClicked: root.setFrequencyValue(0) }\n' + quick_50,
1)
main_path.write_text(main, encoding='utf-8')

# Matrix density: desktop instrument rows, not dashboard cards.
matrix_path = ROOT / 'apps/arstack_studio/qml/SignalMatrix.qml'
matrix = matrix_path.read_text(encoding='utf-8')
matrix = replace_once(matrix,
' Layout.preferredHeight: matrix.compact ? 54 : 60\n',
' Layout.preferredHeight: matrix.compact ? 46 : 50\n',
'SignalMatrix row density')
matrix_path.write_text(matrix, encoding='utf-8')

print('Phase 1 ribbon + compact UX patch applied successfully')
12 changes: 7 additions & 5 deletions .github/workflows/arstack-studio-qt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -209,18 +209,20 @@ jobs:
root=/tmp/arstack-firmware-contract
rm -rf "$root"
mkdir -p "$root/firmware" "$root/tools"
printf 'ARStack-P0-firmware-contract\n' > "$root/firmware/arstack-esp32p4-smv-0.1.0.bin"
sha="$(sha256sum "$root/firmware/arstack-esp32p4-smv-0.1.0.bin" | awk '{print $1}')"
printf 'ARStack-P0-firmware-contract\n' > "$root/firmware/arstack-esp32p4-smv-0.1.1.bin"
sha="$(sha256sum "$root/firmware/arstack-esp32p4-smv-0.1.1.bin" | awk '{print $1}')"
cat > "$root/firmware/firmware-manifest.json" <<EOF
{
"schema": "arstack.studio.firmware.v1",
"product": "ARStack ESP32-P4 SMV Injector",
"chip": "esp32p4",
"chipRevisionPolicy": "pre-v3",
"version": "0.1.0",
"version": "0.1.1",
"sourceCommit": "0123456789abcdef0123456789abcdef01234567",
"protocol": 1,
"capabilities": ["SMV-4I4V", "PROFILE", "LIVE-SETPOINTS", "SESSION-LEASE", "PTP-P2", "SMPSYNCH-AUTO"],
"flashOffset": 0,
"image": "arstack-esp32p4-smv-0.1.0.bin",
"image": "arstack-esp32p4-smv-0.1.1.bin",
"sha256": "$sha"
}
EOF
Expand Down Expand Up @@ -311,4 +313,4 @@ jobs:
/tmp/arstack-studio-launch.log
/tmp/arstack-studio-hidpi.log
if-no-files-found: ignore
retention-days: 7
retention-days: 7
4 changes: 2 additions & 2 deletions .github/workflows/arstack-studio-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ concurrency:
cancel-in-progress: true

env:
ARSTACK_VERSION: '0.1.0'
ARSTACK_VERSION: '0.1.1'
QT_VERSION: '6.10.3'
ESPFLASH_VERSION: '4.5.0'
ESPFLASH_WINDOWS_ZIP_SHA256: '854c82c947c20e7f337f120f0398042ed1760f2269cb0f9be3d2beae3b66fbfb'
Expand Down Expand Up @@ -324,4 +324,4 @@ jobs:
files: |
release/ARStack-Studio-${{ env.ARSTACK_VERSION }}-win-x64-portable.zip
release/ARStack-Studio-${{ env.ARSTACK_VERSION }}-win-x64-setup.exe
release/SHA256SUMS.txt
release/SHA256SUMS.txt
84 changes: 84 additions & 0 deletions .github/workflows/arstack-studio-windows-lifecycle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: ARStack Studio Windows Lifecycle

on:
push:
paths:
- 'apps/arstack_studio/**'
- '.github/workflows/arstack-studio-windows-lifecycle.yml'
pull_request:
paths:
- 'apps/arstack_studio/**'
- '.github/workflows/arstack-studio-windows-lifecycle.yml'

permissions:
contents: read

concurrency:
group: arstack-studio-windows-lifecycle-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
close-exit-reopen:
name: Close -> process exit -> immediate reopen
runs-on: windows-2022
timeout-minutes: 12
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Qt
uses: jurplel/install-qt-action@v4
with:
version: '6.10.3'
arch: win64_msvc2022_64
modules: 'qtserialport'
cache: true
- name: Configure Studio
shell: powershell
run: |
cmake -S apps/arstack_studio -B build-arstack-studio -G "Visual Studio 17 2022" -A x64 `
-DARIEC61850_WARNINGS_AS_ERRORS=ON
if ($LASTEXITCODE -ne 0) { throw "Studio configure failed: $LASTEXITCODE" }
- name: Build actual Studio executable
shell: powershell
run: |
cmake --build build-arstack-studio --config Release --target arstack_studio --parallel
if ($LASTEXITCODE -ne 0) { throw "Studio build failed: $LASTEXITCODE" }
- name: Two-cycle normal-close lifecycle regression
shell: powershell
run: |
$ErrorActionPreference = 'Stop'
$env:QT_QPA_PLATFORM = 'offscreen'
$env:QT_QUICK_BACKEND = 'software'
$candidate = Get-ChildItem -Path (Join-Path $PWD 'build-arstack-studio') -Recurse -File |
Where-Object { $_.Name -ieq 'arstack_studio.exe' -or $_.Name -ieq 'ARStackStudio.exe' } |
Select-Object -First 1
if ($null -eq $candidate) { throw 'Built ARStack Studio executable was not found.' }
$exe = $candidate.FullName
function Invoke-LifecycleCycle([int]$Cycle) {
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
$startInfo.FileName = $exe
$startInfo.Arguments = '--check-app-lifecycle'
$startInfo.UseShellExecute = $false
$startInfo.CreateNoWindow = $true
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $startInfo
if (-not $process.Start()) {
$process.Dispose()
throw "Cycle ${Cycle}: failed to start Studio process."
}
$pidValue = $process.Id
if (-not $process.WaitForExit(12000)) {
Stop-Process -Id $pidValue -Force -ErrorAction SilentlyContinue
$process.Dispose()
throw "Cycle ${Cycle}: Studio process did not exit within 12 seconds after normal primary-window close."
}
$exitCode = $process.ExitCode
$process.Dispose()
if ($exitCode -ne 0) { throw "Cycle ${Cycle}: Studio lifecycle coordinator exited with code $exitCode." }
if (Get-Process -Id $pidValue -ErrorAction SilentlyContinue) { throw "Cycle ${Cycle}: Studio PID $pidValue remained alive after native WaitForExit." }
Write-Host "Cycle $Cycle PASS: normal close -> clean worker retirement -> PID exit."
}
Invoke-LifecycleCycle 1
Start-Sleep -Milliseconds 250
Invoke-LifecycleCycle 2
Write-Host 'Windows lifecycle gate: PASS · normal close -> process exit -> lock release -> immediate reopen -> clean exit'
53 changes: 53 additions & 0 deletions .github/workflows/esp32p4-ptp-lab.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ on:
permissions:
contents: read

concurrency:
group: esp32p4-ptp-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
build-esp32p4-ptp:
strategy:
Expand All @@ -33,6 +37,7 @@ jobs:
role_value: 1
name: build-esp32p4-ptp-${{ matrix.role_name }}
runs-on: ubuntu-latest
timeout-minutes: 25
container:
image: espressif/idf:v5.5.4
defaults:
Expand All @@ -45,6 +50,54 @@ jobs:
- name: Select ESP32-P4 target
run: source /opt/esp/idf/export.sh && idf.py set-target esp32p4

- name: Verify PTP source readiness and ESP-IDF 5.5 TX contract
shell: bash
run: |
python - <<'PY'
from pathlib import Path
eth = Path('main/ethernet_port.c').read_text(encoding='utf-8')
app = Path('main/app_main.cpp').read_text(encoding='utf-8')
ptp = Path('main/ptp_lab_task.cpp').read_text(encoding='utf-8')
receiver = Path('main/ptp_receiver_task.cpp').read_text(encoding='utf-8')

# Never allow the old premature boot path back into Ethernet init.
if 'ar_ptp_lab_try_start(handle)' in eth:
raise SystemExit('PTP must not auto-start inside Ethernet initialization before app_main starts Ethernet')
if 'ESP_ERROR_CHECK(esp_eth_start(eth_handle));' not in app:
raise SystemExit('app_main must explicitly start Ethernet before operator-controlled PTP can run')

# ESP-IDF 5.5 esp_eth_transmit_ctrl_vargs forwards argc as the number
# of variadic arguments. ESP32-P4 EMAC computes buf_num = argc / 2,
# so one frame is two varargs: buffer pointer + buffer length.
tx_signature = '2U,\n frame.data(),\n frame.size()'
if tx_signature not in ptp:
raise SystemExit('PTP SOURCE timestamp TX must pass argc=2 for one buffer/length frame on ESP-IDF 5.5')
if tx_signature not in receiver:
raise SystemExit('PTP RECEIVER timestamp TX must pass argc=2 for one buffer/length frame on ESP-IDF 5.5')
if 'argc counts buffer/length pairs' in ptp or 'argc counts buffer/length pairs' in receiver:
raise SystemExit('stale/incorrect esp_eth_transmit_ctrl_vargs argc documentation remains')
if 'PTP hardware-timestamp TX failed:' not in ptp or 'without a valid hardware timestamp' not in ptp:
raise SystemExit('PTP SOURCE must distinguish driver TX errors from invalid timestamp evidence')

# Admission truth is the stronger bounded runtime contract: explicit
# operator start is accepted only after real Announce + Sync + Follow_Up TX.
required = [
'const bool started = ar_ptp_lab_try_start(s_eth_handle);',
'ar_esp32p4_restore_normal_emac();',
'g_ptp_ready',
'PTP source readiness timeout',
'g_announce_sent.load',
'g_sync_sent.load',
'g_follow_up_sent.load',
'return g_ptp_ready.load',
]
combined = eth + '\n' + ptp
missing = [token for token in required if token not in combined]
if missing:
raise SystemExit('PTP verified-start contract missing: ' + ', '.join(missing))
print('PTP contract: post-Ethernet start + ESP-IDF 5.5 argc=2 timestamp TX + verified core timing + rollback')
PY

- name: Enable guarded PTP-P2 profile
shell: bash
run: |
Expand Down
Loading
Loading