From 7723541d543e30d2f74814aaa0a2de69bafc6850 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 5 Nov 2026 04:47:08 +0700 Subject: [PATCH 1/5] release: verify pinned RC1 assets and create stable portable plus setup ZIP without binary rebuild --- scripts/promote_processbus_dual_stable.py | 158 ++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 scripts/promote_processbus_dual_stable.py diff --git a/scripts/promote_processbus_dual_stable.py b/scripts/promote_processbus_dual_stable.py new file mode 100644 index 000000000..2acfdd77a --- /dev/null +++ b/scripts/promote_processbus_dual_stable.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +"""Promote user-accepted Process Bus Simulator RC1 to stable packaging. + +The Studio executable, Linux AppImage, and both ESP32-P4 firmware images are +copied from immutable SHA-pinned RC1 assets. No application/firmware rebuilds. +The Windows Inno stable installer is compiled separately by GitHub Actions. +""" +import argparse +import hashlib +import json +import pathlib +import shutil +import zipfile + +RC_TAG = "processbus-sim-dual-rc1-20261105" +STABLE_TAG = "processbus-sim-dual-stable-20261105" +SOURCE_SHA = "ab646b58da601569c956f2d19ff45100129f19a0" +MERGED_SHA = "525902548504aa997f868c43f51208fb13beecaf" +EXPECTED = { + "ARStack-Studio-Preview-win-x64-portable.zip": "47d9581b8d94edc8c75dcaa99d5623cf7f9f9a9d018970cb394962d7ca2270de", + "ARStack-Studio-linux-x86_64.AppImage": "36005a79d01470f5bd0f4667cc883a107927562854756c9085427fff4949c17f", + "arstack-esp32p4-smv-0.1.1.bin": "39963b6e531ada33a912b6e6a08915e038d4d6d19dcf8a4354756d5349d94091", + "arstack-esp32p4-smv-0.1.1-v3x.bin": "a7628f912dcf749d0591ffdb5410e50c82864b61b3b25fa5e1cdb00be9754a28", + "firmware-catalog.json": "cbb43524e0eeb54eab8fa57dd1606fa885e7ec946bcc415ca0b0491c7025096b", + "firmware-manifest.json": "f624aeb0efd72b0813503abf267c62edc689c47357323d91be40fe031ade8b83", +} +STABLE_NOTES = """# ARStack Process Bus Simulator - Dual Firmware Stable + +Public stable release promoted from operator-tested RC1. + +**Preferred Windows download: ARStack-Studio-win-x64-setup.zip.** +Extract the ZIP and run the contained ARStack-Studio-win-x64-setup.exe. +Also available: a direct Setup EXE, Windows Portable ZIP, and Linux AppImage. + +The executable for Studio, Linux AppImage, and both ESP32-P4 images are +**bit-identical to the tested RC1**. Only Windows stable-channel installer +packaging and release metadata changed. This is not a new firmware build. + +Supports the existing IEC 61850 4I+4V SV simulation, SCL engineering profiles, +and GPS-style PTP Grandmaster simulation. The dual firmware bundle includes +separate pre-v3 and v3.x images with a verified revision-aware catalog. +The M4 three-MU software controls are integrated; expanded multi-MU load tests +and broad v3.x hardware interoperability remain under field assessment. + +The Windows stable Inno installer has its own AppId; previously installed +Preview builds can be uninstalled separately after confirming stable works. +ZIP does not eliminate Windows SmartScreen/antivirus inspection of unsigned EXEs. + +Use on an isolated laboratory process bus, never an energized protection system. +Historical M3.H.3 stable release and the user-tested RC1 are preserved. +""" + +def digest(path): + h = hashlib.sha256() + with open(path, "rb") as stream: + for block in iter(lambda: stream.read(1024 * 1024), b""): + h.update(block) + return h.hexdigest() + +def require(condition, message): + if not condition: + raise ValueError(message) + +def prepare(rc, release_meta, stage, out): + meta = json.loads(release_meta.read_text(encoding="utf-8-sig")) + require(meta["tag_name"] == RC_TAG and meta["prerelease"] and not meta["draft"], "Wrong RC release") + assets = {a["name"]: a for a in meta["assets"]} + for name, expected in EXPECTED.items(): + file = rc / name + require(file.is_file() and digest(file) == expected, "Pinned asset changed: " + name) + require(assets[name].get("digest") == "sha256:" + expected, "Release registry digest mismatch: " + name) + original = json.loads((rc / "release-provenance.json").read_text(encoding="utf-8-sig")) + require(original["sourceHead"] == SOURCE_SHA and original["mergedMain"] == MERGED_SHA and original["sourcePR"] == 359, "Wrong RC provenance") + catalog = json.loads((rc / "firmware-catalog.json").read_text(encoding="utf-8-sig")) + manifest = json.loads((rc / "firmware-manifest.json").read_text(encoding="utf-8-sig")) + require(catalog["sourceCommit"] == manifest["sourceCommit"] == SOURCE_SHA, "Firmware/Studio revision drift") + require(manifest["bootSimulatedGlobalGmEnabled"] and manifest["phaseSyncPreview"], "Lost simulated GM") + require({i["policy"] for i in catalog["images"]} == {"pre-v3", "v3.x"} and len(catalog["images"]) == 2, "Must provide exactly two revision images") + for entry in catalog["images"]: + require(digest(rc / entry["image"]) == entry["sha256"], "Corrupt revision image") + stage.mkdir(parents=True, exist_ok=True) + out.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(rc / "ARStack-Studio-Preview-win-x64-portable.zip") as archive: + for entry in archive.infolist(): + p = pathlib.PurePosixPath(entry.filename) + require(not p.is_absolute() and ".." not in p.parts and not entry.filename.startswith("\\"), "Unsafe ZIP entry") + archive.extractall(stage) + for rel in ("ARStackStudio.exe", "ARStackStudio.ico", "tools/espflash.exe", + "firmware/firmware-manifest.json", "firmware/firmware-catalog.json"): + require((stage / rel).is_file(), "Missing portable file: " + rel) + require((stage / "firmware/firmware-catalog.json").read_bytes() == (rc / "firmware-catalog.json").read_bytes(), "Packaged catalog differs") + for entry in catalog["images"]: + require(digest(stage / "firmware" / entry["image"]) == entry["sha256"], "Portable firmware mismatch") + (stage / "PREVIEW-PROVENANCE.json").unlink(missing_ok=True) + (stage / "RELEASE-PROVENANCE.json").write_text(json.dumps({ + "schema": "arstack.studio.release-provenance.v1", + "channel": "stable", "sourceRelease": RC_TAG, + "studioFirmwareSource": SOURCE_SHA, "sourceMain": MERGED_SHA, + "studioExeSha256": digest(stage / "ARStackStudio.exe"), + "firmwareCatalogPreserved": True + }, indent=2, sort_keys=True) + "\n", encoding="utf-8") + with zipfile.ZipFile(out / "ARStack-Studio-win-x64-portable.zip", "w", compression=zipfile.ZIP_DEFLATED, compresslevel=6) as archive: + for f in sorted(stage.rglob("*")): + if f.is_file(): + archive.write(f, f.relative_to(stage).as_posix()) + for filename in ("ARStack-Studio-linux-x86_64.AppImage", + "ARStack-Studio-linux-x86_64.sha256", + "arstack-esp32p4-smv-0.1.1.bin", + "arstack-esp32p4-smv-0.1.1-v3x.bin", + "firmware-manifest.json", "firmware-catalog.json"): + shutil.copy2(rc / filename, out / filename) + require(digest(out / "ARStack-Studio-linux-x86_64.AppImage") == EXPECTED["ARStack-Studio-linux-x86_64.AppImage"], "Linux binary drift") + print("RC1 EXACT-BINARY DUAL-SILICON STABLE STAGING: PASS") + +def finish(stage, out): + setup = out / "ARStack-Studio-win-x64-setup.exe" + require(setup.is_file(), "Stable Inno Setup missing") + zpath = out / "ARStack-Studio-win-x64-setup.zip" + with zipfile.ZipFile(zpath, "w", compression=zipfile.ZIP_STORED) as archive: + archive.write(setup, setup.name) + with zipfile.ZipFile(zpath) as archive: + require(archive.namelist() == [setup.name], "Setup ZIP must contain one installer") + require(hashlib.sha256(archive.read(setup.name)).hexdigest() == digest(setup), "Setup ZIP checksum mismatch") + (out / "RELEASE_NOTES.md").write_text(STABLE_NOTES, encoding="utf-8") + (out / "release-provenance.json").write_text(json.dumps({ + "schema": "arstack.public-release.provenance.v1", + "channel": "stable", "tag": STABLE_TAG, "derivedFrom": RC_TAG, + "sourceHead": SOURCE_SHA, "mergedMain": MERGED_SHA, + "applicationFirmwareRecompiled": False, "stableInstallerRepackaged": True, + "windowsPreferredAsset": zpath.name, + "firmwarePolicies": ["pre-v3", "v3.x"] + }, indent=2, sort_keys=True) + "\n", encoding="utf-8") + files = sorted(x for x in out.iterdir() if x.is_file() and x.name not in ("RELEASE_NOTES.md", "SHA256SUMS.txt")) + require(len(files) >= 10, "Stable release missing assets") + sums = "".join(digest(f) + " " + f.name + "\n" for f in files) + (out / "SHA256SUMS.txt").write_text(sums, encoding="ascii") + for f in files: + require(digest(f) in sums, "Checksum instability") + require(digest(stage / "ARStackStudio.exe") == json.loads((stage / "RELEASE-PROVENANCE.json").read_text())["studioExeSha256"], "Stable Studio binary drift") + print("STABLE INSTALLER ZIP AND ALL ASSET SHA256: PASS") + for f in sorted(out.iterdir()): + print(f.name, f.stat().st_size) + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("stage_action", choices=("prepare", "finish")) + ap.add_argument("--rc", type=pathlib.Path, required=True) + ap.add_argument("--release-meta", type=pathlib.Path, required=True) + ap.add_argument("--stage", type=pathlib.Path, required=True) + ap.add_argument("--out", type=pathlib.Path, required=True) + args = ap.parse_args() + if args.stage_action == "prepare": + prepare(args.rc, args.release_meta, args.stage, args.out) + else: + finish(args.stage, args.out) + +if __name__ == "__main__": + main() From 8fca7532a652efaf09a1cc63d29f2d15a862778e Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 5 Nov 2026 04:48:45 +0700 Subject: [PATCH 2/5] release: promote validated dual firmware RC1 as stable, with Windows Setup ZIP preferred --- .../processbus-dual-stable-publish.yml | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 .github/workflows/processbus-dual-stable-publish.yml diff --git a/.github/workflows/processbus-dual-stable-publish.yml b/.github/workflows/processbus-dual-stable-publish.yml new file mode 100644 index 000000000..e0875c6f7 --- /dev/null +++ b/.github/workflows/processbus-dual-stable-publish.yml @@ -0,0 +1,114 @@ +name: Process Bus Dual Firmware Stable Promotion + +# Repackage tested RC1. NO firmware or Qt compilation. +on: + push: + branches: [main] + paths: + - docs/releases/PROCESSBUS_DUAL_STABLE_LAUNCH.md + +permissions: + contents: write + +concurrency: + group: arstack-dual-stable-20261105 + cancel-in-progress: false + +env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RC_TAG: processbus-sim-dual-rc1-20261105 + STABLE_TAG: processbus-sim-dual-stable-20261105 + RC_MERGED_MAIN: 525902548504aa997f868c43f51208fb13beecaf + +jobs: + verified-stable-publication: + runs-on: windows-2022 + timeout-minutes: 40 + steps: + - name: Checkout verified stable Inno script + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Download pinned tested RC1 and verify exact firmware Studio Linux + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $rc = Join-Path $env:RUNNER_TEMP 'arstack-dual-rc1' + New-Item -ItemType Directory -Force -Path $rc | Out-Null + & gh release download $env:RC_TAG --repo $env:GITHUB_REPOSITORY --dir $rc + if ($LASTEXITCODE -ne 0) { throw 'RC1 download failed' } + $meta = Join-Path $env:RUNNER_TEMP 'arstack-rc1-release.json' + & gh api "repos/$env:GITHUB_REPOSITORY/releases/tags/$env:RC_TAG" | Out-File -LiteralPath $meta -Encoding utf8 + if ($LASTEXITCODE -ne 0) { throw 'RC1 metadata failed' } + python scripts/promote_processbus_dual_stable.py prepare --rc $rc --release-meta $meta --stage (Join-Path $env:RUNNER_TEMP 'arstack-stable-stage') --out (Join-Path $PWD 'dist-stable') + if ($LASTEXITCODE -ne 0) { throw 'Provenance / dual-revision validation failed' } + + - name: Compile Stable Inno installer from identical tested RC1 binaries + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + choco install innosetup --version 6.7.1 -y --no-progress + if ($LASTEXITCODE -ne 0) { throw 'Inno Setup unavailable' } + $compiler = Join-Path ([Environment]::GetFolderPath('ProgramFilesX86')) 'Inno Setup 6\ISCC.exe' + $iss = (Resolve-Path 'apps\arstack_studio\packaging\ARStackStudio.iss').Path + $stage = Join-Path $env:RUNNER_TEMP 'arstack-stable-stage' + $out = Join-Path $PWD 'dist-stable' + & $compiler '/DSTUDIO_VERSION=0.1.1' "/DSTAGE_DIR=$stage" "/O$out" $iss + if ($LASTEXITCODE -ne 0 -or -not (Test-Path (Join-Path $out 'ARStack-Studio-win-x64-setup.exe'))) { + throw 'Stable-channel installer missing' + } + + - name: Windows Stable AppId install firmware contract and clean uninstall + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $installer = Join-Path $PWD 'dist-stable\ARStack-Studio-win-x64-setup.exe' + $target = Join-Path $env:RUNNER_TEMP 'ARStackStableInstall' + $arguments = @('/VERYSILENT','/SUPPRESSMSGBOXES','/NORESTART','/CLOSEAPPLICATIONS',("/DIR=" + $target)) + $process = Start-Process -FilePath $installer -ArgumentList $arguments -Wait -PassThru + if ($process.ExitCode -ne 0) { throw 'Stable installer execution failed' } + $app = Join-Path $target 'ARStackStudio.exe' + $stableKey = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\ARStack61850.Studio.Stable_is1' + $previewKey = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\ARStack61850.Studio.Preview_is1' + if (-not (Test-Path $app) -or -not (Test-Path $stableKey) -or (Test-Path $previewKey)) { + throw 'Stable installer AppId is incorrect' + } + $source = Join-Path $env:RUNNER_TEMP 'arstack-stable-stage\ARStackStudio.exe' + if ((Get-FileHash $app -Algorithm SHA256).Hash -ne (Get-FileHash $source -Algorithm SHA256).Hash) { + throw 'Installed executable is not the RC1 executable' + } + $env:ARSTACK_STUDIO_FIRMWARE_DIR = Join-Path $target 'firmware' + $env:ARSTACK_STUDIO_ESPFLASH = Join-Path $target 'tools\espflash.exe' + $env:ESPFLASH_SKIP_UPDATE_CHECK = 'true' + foreach ($check in @('--check-firmware-contract','--check-p0-policy','--check-reference-template','--check-app-lifecycle')) { + $probe = Start-Process -FilePath $app -ArgumentList $check -Wait -PassThru + if ($probe.ExitCode -ne 0) { throw "Stable Studio failed $check" } + } + $remover = Join-Path $target 'unins000.exe' + $gone = Start-Process -FilePath $remover -ArgumentList @('/VERYSILENT','/SUPPRESSMSGBOXES','/NORESTART') -Wait -PassThru + if ($gone.ExitCode -ne 0 -or (Test-Path $app) -or (Test-Path $stableKey)) { throw 'Stable uninstall failed' } + Write-Host 'STABLE_INSTALL_SMOKE: PASS' + + - name: Create preferred Setup ZIP and verify all release hashes + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + python scripts/promote_processbus_dual_stable.py finish --rc (Join-Path $env:RUNNER_TEMP 'arstack-dual-rc1') --release-meta (Join-Path $env:RUNNER_TEMP 'arstack-rc1-release.json') --stage (Join-Path $env:RUNNER_TEMP 'arstack-stable-stage') --out (Join-Path $PWD 'dist-stable') + if ($LASTEXITCODE -ne 0) { throw 'Preferred ZIP hash verification failed' } + + - name: Publish immutable stable GitHub release as latest + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $out = Join-Path $PWD 'dist-stable' + $assets = @(Get-ChildItem -LiteralPath $out -File | ForEach-Object { $_.FullName }) + if ($assets.Count -lt 11) { throw 'Incomplete stable assets' } + $arguments = @('release','create',$env:STABLE_TAG) + $assets + @('--repo',$env:GITHUB_REPOSITORY,'--target',$env:RC_MERGED_MAIN,'--title','ARStack Process Bus Simulator - Dual Firmware Stable','--notes-file',(Join-Path $out 'RELEASE_NOTES.md'),'--latest') + & gh @arguments + if ($LASTEXITCODE -ne 0) { throw 'Stable publishing failed or immutable tag already exists' } + $published = (& gh api "repos/$env:GITHUB_REPOSITORY/releases/tags/$env:STABLE_TAG" | ConvertFrom-Json) + if ($published.draft -or $published.prerelease -or $published.assets.Count -ne $assets.Count) { + throw 'Stable release not fully published' + } + Write-Host "STABLE_RELEASE_URL=$($published.html_url)" From dfca646faeea62f54565e259d4878d4a17325ba6 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 5 Nov 2026 04:49:33 +0700 Subject: [PATCH 3/5] release: authorize stable promotion of user-tested RC1 and preferred ZIP setup --- .../releases/PROCESSBUS_DUAL_STABLE_LAUNCH.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 docs/releases/PROCESSBUS_DUAL_STABLE_LAUNCH.md diff --git a/docs/releases/PROCESSBUS_DUAL_STABLE_LAUNCH.md b/docs/releases/PROCESSBUS_DUAL_STABLE_LAUNCH.md new file mode 100644 index 000000000..87a39a995 --- /dev/null +++ b/docs/releases/PROCESSBUS_DUAL_STABLE_LAUNCH.md @@ -0,0 +1,21 @@ +# Process Bus Simulator Dual Firmware — Stable Release Promotion + +**Release tag:** `processbus-sim-dual-stable-20261105` +**Source accepted by operator:** Public RC1 `processbus-sim-dual-rc1-20261105` +**Exact RC1 firmware/Studio source:** `ab646b58da601569c956f2d19ff45100129f19a0` +**RC1 integrated main SHA:** `525902548504aa997f868c43f51208fb13beecaf` + +This file is a one-time, post-merge trigger for the existing-artifact stable promotion +in `.github/workflows/processbus-dual-stable-publish.yml`. + +## Product release contract + +- ARStack61850 is an IEC 61850 Process Bus **Merging Unit + GPS-style PTP Grandmaster simulator** for isolated laboratory current/voltage SV injection and SCL-driven engineering. Emulated PTP and SV must work as though genuine process-bus devices were connected. +- **No C++ Studio, Qt, ESP-IDF, SV encoder, PTP runtime, or firmware rebuild.** Promote the exact operator-tested public RC1 binaries, matching the two distinct ESP32-P4 pre-v3 and v3.x images and catalog by pinned SHA256. +- The one necessary packaging change: compile the already-gated Inno Setup source **without ARSTACK_PREVIEW**, using the stable AppId and installer identity. Run fresh Windows stable install, exact Studio binary/firmware contract, and clean uninstall checks. +- Package `ARStack-Studio-win-x64-setup.zip` containing only `ARStack-Studio-win-x64-setup.exe` as the **preferred browser download**. Provide portable ZIP, Setup EXE, Linux AppImage, firmware images, revision catalog, manifest, release notes, SHA256SUMS and source provenance separately. +- Create a **new public non-prerelease stable release** and make it latest. Do not edit the accepted RC1 tag or historic M3.H.3 stable tag/assets. Publication must fail closed on provenance/hash mismatch or CI/package test failure. +- ZIP helps with browser download handling but does **not** bypass Microsoft SmartScreen or security checks. Keep unsigned EXE provenance and installation instructions clear. +- Stable release reflects operator-tested RC1 behavior; avoid making unsupported claims that 3x4000fps physical HIL on every PCB or certified UTC/GPS hardware were proven. + +**Gate:** Merge this release PR only after static validation and no outstanding review issues; the merge starts the single Windows packaging/publishing job without reopening expensive firmware CI. From 67da1b6bb70adb7cb1de272b77b0880815de2782 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 5 Nov 2026 04:50:19 +0700 Subject: [PATCH 4/5] site: prefer stable Setup ZIP, keep old EXE fallback, and route dual firmware via revision catalog --- index.html | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 6704da570..cc823b225 100644 --- a/index.html +++ b/index.html @@ -60,12 +60,12 @@

Your own IEC 61850 Process Bus lab.

ARStack StudioWindows setup · autonomous board state
ESP32-P4 EthernetEMAC hardware timestamp + 4 kHz SV grid
PTP · Announce / Sync / Follow_Up
SV · 4I+4V · smpSynch AUTO

M3.H.3 on-wire lab capture: 4,705,090 SV frames over 19 min 36 s, 3,999.886 fps, zero missing/duplicate/reordered/malformed SV; 3,528 PTP packets observed and smpSynch=2 on the received stream. View the engineering capture results.

-

Next: multiple virtual Merging Units from one board.

ARStack is built for practical digital-substation engineers. Our next priority is Multi-SV on one ESP32-P4 plus SCL-driven Merging Unit simulation: open an SCD/CID/IID, select supported SV streams and deploy their identities and DataSet layouts without building a rack of physical MUs.

Multiple SV publishers

One ESP32-P4, multiple independently configured SV streams, each with its own identity and sample counter.

SCL file-based configuration

Choose Merging Unit streams from IEC 61850 engineering files and map supported DataSet leaves to generated waveforms.

Engineer-first workflow

Open file → select stream(s) → assign values → Start → observe real traffic in a relay/BCU test network.

Multi-stream publishing and the full SCL-driven MU workflow are planned next; M3.H.3 currently ships the proven single 4I+4V SV stream. See the engineering roadmap.

+

Next: multiple virtual Merging Units from one board.

ARStack is built for practical digital-substation engineers. Our next priority is Multi-SV on one ESP32-P4 plus SCL-driven Merging Unit simulation: open an SCD/CID/IID, select supported SV streams and deploy their identities and DataSet layouts without building a rack of physical MUs.

Multiple SV publishers

One ESP32-P4, multiple independently configured SV streams, each with its own identity and sample counter.

SCL file-based configuration

Choose Merging Unit streams from IEC 61850 engineering files and map supported DataSet leaves to generated waveforms.

Engineer-first workflow

Open file → select stream(s) → assign values → Start → observe real traffic in a relay/BCU test network.

M4 software supports SCL-driven virtual MU streams on one ESP32-P4; extended three-stream physical load acceptance is still being gathered. The M3.H.3 measurements above refer to the historical single-stream release. See the engineering roadmap.

See ARStack Studio in action.

Configure IEC 61850 waveform scenarios, inspect phasors, and prepare process-bus experiments in one native desktop workspace.

ARStack Studio Windows engineering interface with 4I+4V signal controls, voltage and current phasors and waveform preview
Real Windows Qt application screenshot · 960 × 680

First packet in four steps.

Install StudioUse Windows Setup or Portable; both include the matched firmware and flashing tool.
Flash ESP32-P4Connect USB OTG and install the matching firmware from the release.
Select lab timingUse PTP SOURCE / INTERNAL_GM_ONLY and smpSynch AUTO on an isolated Ethernet LAN.
Start and captureRun 4I+4V, then inspect SV 0x88BA and PTP 0x88F7 in ARSAS, DigSubAnalyzer or Wireshark.

Safety boundary: Never attach the lab PTP GM or SV publisher to a live substation process bus. Use an isolated test LAN or VLAN. ARStack is an open-source IEC 61850 engineering lab tool; no external GPS or protection relay is needed for the basic simulation.

-

Download the latest release.

Choose the installer, portable application or matched board firmware from the latest stable GitHub release. Downloads update automatically when a new verified release is published.

+

Download the latest release.

Prefer the Windows Setup ZIP, extract it and run the installer. Portable ZIP requires no installation; select revision-matched ESP32-P4 firmware from the latest release catalog. Downloads update automatically when a new verified release is published.

Windows Installer

Windows x64 · setup and bundled dependencies

Setup EXE ↗

ESP32-P4 Firmware

Match the firmware to your installed Studio release

Firmware BIN ↗
@@ -89,9 +89,9 @@

Your own IEC 61850 Process Bus lab.

typeof x.browser_download_url==='string' && x.browser_download_url.startsWith('https://github.com/masarray/arstack61850/releases/download/')); const resolved={ - windows:find(/(?:win|windows).*setup\.exe$/i), + windows:find(/(?:win|windows).*setup\.zip$/i) || find(/(?:win|windows).*setup\.exe$/i), portable:find(/(?:win|windows).*portable\.zip$/i), - firmware:find(/firmware.*\.bin$/i), + firmware:assets.some(x=>x.name==='firmware-catalog.json') ? null : find(/firmware.*\.bin$/i), linux:find(/linux.*\.AppImage$/i) }; for(const [key,asset] of Object.entries(resolved)){ @@ -100,6 +100,12 @@

Your own IEC 61850 Process Bus lab.

if(!asset) link.title='View available files in the latest release'; }); } + document.querySelectorAll('[data-asset="windows"]').forEach(link=>{ + const preferredZip=resolved.windows && /setup\.zip$/i.test(resolved.windows.name); + link.textContent=link.classList.contains('btn') + ? (preferredZip ? 'Download Windows Setup ZIP ↓' : 'Download latest Windows Setup ↓') + : (preferredZip ? 'Setup ZIP ↗' : 'Setup EXE ↗'); + }); if(resolved.linux) document.getElementById('linux-download').hidden=false; const label=document.querySelector('[data-latest-label]'); if(label) label.textContent='Latest stable release · '+release.tag_name; From f624cc6ee721060c49b75066c9d171bbd8fe16fa Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 5 Nov 2026 04:55:57 +0700 Subject: [PATCH 5/5] release: explicitly qualify v3.x firmware hardware as engineering preview within stable Studio --- scripts/promote_processbus_dual_stable.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/promote_processbus_dual_stable.py b/scripts/promote_processbus_dual_stable.py index 2acfdd77a..6ab1847a7 100644 --- a/scripts/promote_processbus_dual_stable.py +++ b/scripts/promote_processbus_dual_stable.py @@ -39,8 +39,13 @@ Supports the existing IEC 61850 4I+4V SV simulation, SCL engineering profiles, and GPS-style PTP Grandmaster simulation. The dual firmware bundle includes separate pre-v3 and v3.x images with a verified revision-aware catalog. -The M4 three-MU software controls are integrated; expanded multi-MU load tests -and broad v3.x hardware interoperability remain under field assessment. +The M4 three-MU software controls are integrated. Sustained 3x4000fps +field timing acceptance remains separate from the operator-tested RC1 release. +**The v3.x firmware image is distributed as an ENGINEERING PREVIEW within this +stable Studio package**: v3.x board/PHY boot, SV/PTP Ethernet capture, recovery, +autonomous operation and broad hardware compatibility are still undergoing +physical acceptance. Existing pre-v3 test evidence must not be presented as +v3.x evidence. The revision-aware catalog retains its original preview channel. The Windows stable Inno installer has its own AppId; previously installed Preview builds can be uninstalled separately after confirming stable works. @@ -128,7 +133,8 @@ def finish(stage, out): "sourceHead": SOURCE_SHA, "mergedMain": MERGED_SHA, "applicationFirmwareRecompiled": False, "stableInstallerRepackaged": True, "windowsPreferredAsset": zpath.name, - "firmwarePolicies": ["pre-v3", "v3.x"] + "firmwarePolicies": ["pre-v3", "v3.x"], + "v3xHardwareQualification": "engineering-preview; physical acceptance pending" }, indent=2, sort_keys=True) + "\n", encoding="utf-8") files = sorted(x for x in out.iterdir() if x.is_file() and x.name not in ("RELEASE_NOTES.md", "SHA256SUMS.txt")) require(len(files) >= 10, "Stable release missing assets")