From 3d9a26f53cc2b86c49ccd30d705f8a386ae1ec37 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 22 Oct 2026 07:11:52 +0700 Subject: [PATCH 01/11] fix(ptp): accept IEC power-profile VLAN zero priority tag --- include/ariec61850/time_sync/ptp_runtime.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/ariec61850/time_sync/ptp_runtime.hpp b/include/ariec61850/time_sync/ptp_runtime.hpp index 51e0a2554..9284dc811 100644 --- a/include/ariec61850/time_sync/ptp_runtime.hpp +++ b/include/ariec61850/time_sync/ptp_runtime.hpp @@ -190,9 +190,10 @@ class PtpPublisherRuntime final { error = "VLAN priority must be 0..7"; return false; } - if (options.vlan_id.has_value() && - (*options.vlan_id == 0U || *options.vlan_id > 4094U)) { - error = "VLAN ID must be 1..4094"; + // IEEE 802.1Q priority-tagged VLAN 0 / PCP 4 is the Power + // Utility Profile default. 4095 is reserved; VID 0 is valid. + if (options.vlan_id.has_value() && *options.vlan_id > 4094U) { + error = "VLAN ID must be 0..4094"; return false; } if (options.port_number == 0U) { From 5f660124290452e392d7a93903c4bfd25961a572 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 22 Oct 2026 07:13:03 +0700 Subject: [PATCH 02/11] fix(ptp): default to IEC 61850-9-3 priority-tagged VLAN0 PCP4 --- embedded/esp32p4_smv_injector/main/Kconfig.projbuild | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/embedded/esp32p4_smv_injector/main/Kconfig.projbuild b/embedded/esp32p4_smv_injector/main/Kconfig.projbuild index b7188d5da..06e88cd20 100644 --- a/embedded/esp32p4_smv_injector/main/Kconfig.projbuild +++ b/embedded/esp32p4_smv_injector/main/Kconfig.projbuild @@ -134,14 +134,14 @@ config AR_PTP_PORT_NUMBER depends on AR_PTP_LAB_TX config AR_PTP_VLAN - bool "Tag PTP traffic with IEEE 802.1Q VLAN" - default n + bool "Tag PTP traffic with IEEE 802.1Q VLAN (Power Profile PCP4/VID0)" + default y depends on AR_PTP_LAB_TX config AR_PTP_VLAN_ID - int "PTP VLAN ID" - range 1 4094 - default 100 + int "PTP VLAN ID (0 means 802.1Q priority tag)" + range 0 4094 + default 0 depends on AR_PTP_LAB_TX && AR_PTP_VLAN config AR_PTP_VLAN_PRIORITY From d734cc626d1371a102c1eca8468d332eacb58481 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 22 Oct 2026 07:13:06 +0700 Subject: [PATCH 03/11] fix(firmware): enable PTP priority tagging to match SV VLAN0 PCP4 --- embedded/esp32p4_smv_injector/sdkconfig.defaults | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/embedded/esp32p4_smv_injector/sdkconfig.defaults b/embedded/esp32p4_smv_injector/sdkconfig.defaults index 95776790c..41a7a0580 100644 --- a/embedded/esp32p4_smv_injector/sdkconfig.defaults +++ b/embedded/esp32p4_smv_injector/sdkconfig.defaults @@ -28,7 +28,9 @@ CONFIG_AR_PTP_OPERATING_ROLE=0 CONFIG_AR_PTP_DOMAIN=0 CONFIG_AR_PTP_TRANSPORT_SPECIFIC=0 CONFIG_AR_PTP_PORT_NUMBER=1 -# CONFIG_AR_PTP_VLAN is not set +CONFIG_AR_PTP_VLAN=y +CONFIG_AR_PTP_VLAN_ID=0 +CONFIG_AR_PTP_VLAN_PRIORITY=4 CONFIG_AR_PTP_ANNOUNCE_INTERVAL_MS=1000 CONFIG_AR_PTP_SYNC_INTERVAL_MS=1000 CONFIG_AR_PTP_RESPOND_PDELAY=y From 09ee04fde52d17a482713eb506a08aa431674db7 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 22 Oct 2026 07:13:11 +0700 Subject: [PATCH 04/11] fix(studio): permit power-profile priority-tagged VLAN 0 --- apps/arstack_studio/src/DeviceController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/arstack_studio/src/DeviceController.cpp b/apps/arstack_studio/src/DeviceController.cpp index 94dbf721a..b01aa37d8 100644 --- a/apps/arstack_studio/src/DeviceController.cpp +++ b/apps/arstack_studio/src/DeviceController.cpp @@ -1351,7 +1351,7 @@ QString DeviceController::ptpConfigCommand(const QVariantMap& profile) { !parse("announceIntervalMs", 10000U, announce) || !parse("syncIntervalMs", 5000U, sync) || announce < 100U || sync < 20U || - (profile.value(QStringLiteral("vlanEnabled")).toBool() && vlan == 0U)) { + false) { return {}; } return QStringLiteral("PTP CONFIG %1 %2 %3 %4 %5 %6 %7 %8") From 4fb1819fbdce2bf13ffc362985772e1fa90f4e44 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 22 Oct 2026 07:13:14 +0700 Subject: [PATCH 05/11] fix(studio): offer power-profile VID0 PCP4 by default --- apps/arstack_studio/qml/PtpExpertPanel.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/arstack_studio/qml/PtpExpertPanel.qml b/apps/arstack_studio/qml/PtpExpertPanel.qml index 67f1cd4fc..61a5885e8 100644 --- a/apps/arstack_studio/qml/PtpExpertPanel.qml +++ b/apps/arstack_studio/qml/PtpExpertPanel.qml @@ -545,8 +545,8 @@ SurfacePanel { NumericField { id: announceField; visible: modeCombo.currentIndex !== 0; theme: panel.theme; monoFont: panel.uiFont; text: "1000"; validator: IntValidator { bottom: 100; top: 10000 } } Label { text: "Sync interval (ms)"; visible: modeCombo.currentIndex !== 0; color: panel.theme.muted; font.family: panel.uiFont; font.pixelSize: 9 } NumericField { id: syncField; visible: modeCombo.currentIndex !== 0; theme: panel.theme; monoFont: panel.uiFont; text: "1000"; validator: IntValidator { bottom: 20; top: 5000 } } - StudioDarkCheckBox { id: vlanCheck; theme: panel.theme; uiFont: panel.uiFont; text: "VLAN" } - NumericField { id: vlanField; enabled: vlanCheck.checked; theme: panel.theme; monoFont: panel.uiFont; text: "100"; validator: IntValidator { bottom: 1; top: 4094 } } + StudioDarkCheckBox { id: vlanCheck; theme: panel.theme; uiFont: panel.uiFont; text: "VLAN"; checked: true } + NumericField { id: vlanField; enabled: vlanCheck.checked; theme: panel.theme; monoFont: panel.uiFont; text: "0"; validator: IntValidator { bottom: 0; top: 4094 } } Label { text: "VLAN PCP"; color: panel.theme.muted; font.family: panel.uiFont; font.pixelSize: 9 } NumericField { id: pcpField; enabled: vlanCheck.checked; theme: panel.theme; monoFont: panel.uiFont; text: "4"; validator: IntValidator { bottom: 0; top: 7 } } Label { From d74a5837b22830b19b92c4243ac75df274c6cc84 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 22 Oct 2026 07:13:31 +0700 Subject: [PATCH 06/11] refactor(studio): remove redundant zero-VLAN validation clause --- apps/arstack_studio/src/DeviceController.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/arstack_studio/src/DeviceController.cpp b/apps/arstack_studio/src/DeviceController.cpp index b01aa37d8..942e1031e 100644 --- a/apps/arstack_studio/src/DeviceController.cpp +++ b/apps/arstack_studio/src/DeviceController.cpp @@ -1350,8 +1350,7 @@ QString DeviceController::ptpConfigCommand(const QVariantMap& profile) { !parse("vlanPriority", 7U, pcp) || !parse("announceIntervalMs", 10000U, announce) || !parse("syncIntervalMs", 5000U, sync) || - announce < 100U || sync < 20U || - false) { + announce < 100U || sync < 20U) { return {}; } return QStringLiteral("PTP CONFIG %1 %2 %3 %4 %5 %6 %7 %8") From b099d264e980c445fa04a53c642b7ce469dd884e Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 22 Oct 2026 07:13:52 +0700 Subject: [PATCH 07/11] test(ptp): assert priority-tagged VLAN0 PCP4 on actual PTP Announce bytes --- tests/test_ptp_power_profile.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/test_ptp_power_profile.cpp b/tests/test_ptp_power_profile.cpp index f76527391..30809cf4f 100644 --- a/tests/test_ptp_power_profile.cpp +++ b/tests/test_ptp_power_profile.cpp @@ -131,6 +131,30 @@ void check_source_heartbeat_freshness_and_time_provenance() { explicit_offset.stop(); } +void check_priority_tagged_power_profile_wire() { + auto options = make_source(); + options.vlan_id = 0U; + options.vlan_priority = 4U; + std::string error; + REQUIRE(PtpPublisherRuntime::validate_options(options, error)); + PtpPublisherRuntime source{options}; + REQUIRE(source.start()); + const auto announce = source.prepare_announce(PtpTimestamp{1000U, 0U}); + REQUIRE(announce.has_value()); + const auto& raw = announce->ethernet_frame; + REQUIRE(raw.size() > 18U); + REQUIRE(raw[12] == 0x81U && raw[13] == 0x00U); + REQUIRE(raw[14] == 0x80U && raw[15] == 0x00U); // PCP4, VID0 + REQUIRE(raw[16] == 0x88U && raw[17] == 0xF7U); + PtpFrame parsed{}; + REQUIRE(PtpCodec::try_parse_ethernet_frame(raw, parsed)); + REQUIRE(parsed.vlan_id == std::optional{0U}); + REQUIRE(parsed.header.message_type == PtpMessageType::announce); + source.stop(); + options.vlan_id = 4095U; + REQUIRE(!PtpPublisherRuntime::validate_options(options, error)); +} + void check_ptp_wire_sequence_and_identity() { auto options = make_source(); constexpr std::uint8_t domain = 93U; // supported alternate network domain From 9530f07866cd52652971f8f51b6cd55c7770456d Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 22 Oct 2026 07:14:07 +0700 Subject: [PATCH 08/11] test(ptp): run PTP VLAN0 PCP4 wire regression --- tests/test_ptp_power_profile.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_ptp_power_profile.cpp b/tests/test_ptp_power_profile.cpp index 30809cf4f..ca69f04ee 100644 --- a/tests/test_ptp_power_profile.cpp +++ b/tests/test_ptp_power_profile.cpp @@ -333,6 +333,7 @@ int main() { check_source_readiness_requires_fresh_wire_proof(); check_source_heartbeat_freshness_and_time_provenance(); check_ptp_wire_sequence_and_identity(); + check_priority_tagged_power_profile_wire(); check_vlan_encoding_remains_configurable(); check_tai_seed_and_truthful_time_properties(); std::cout << "PTP Power Profile wire and configuration checks passed\n"; From 8db8a70fa6f7feed04dc163ccb15deaca684982c Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 22 Oct 2026 07:14:11 +0700 Subject: [PATCH 09/11] ci(ptp): enforce VLAN0 PCP4 tagging in matched ESP32-P4 preview firmware --- .github/workflows/esp32p4-smv-injector.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/esp32p4-smv-injector.yml b/.github/workflows/esp32p4-smv-injector.yml index 28d37bd06..96a2caefd 100644 --- a/.github/workflows/esp32p4-smv-injector.yml +++ b/.github/workflows/esp32p4-smv-injector.yml @@ -157,6 +157,9 @@ 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_VLAN=y' sdkconfig + grep -qx 'CONFIG_AR_PTP_VLAN_ID=0' sdkconfig + grep -qx 'CONFIG_AR_PTP_VLAN_PRIORITY=4' 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' From 43e0a65bef7be0a71401f1ef6edc9a4743e15156 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 22 Oct 2026 07:17:35 +0700 Subject: [PATCH 10/11] fix(test): avoid implicit 32-to-16-bit narrowing in MSVC PTP VLAN vectors --- tests/test_ptp_power_profile.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_ptp_power_profile.cpp b/tests/test_ptp_power_profile.cpp index ca69f04ee..f78f8eb49 100644 --- a/tests/test_ptp_power_profile.cpp +++ b/tests/test_ptp_power_profile.cpp @@ -133,7 +133,7 @@ void check_source_heartbeat_freshness_and_time_provenance() { void check_priority_tagged_power_profile_wire() { auto options = make_source(); - options.vlan_id = 0U; + options.vlan_id = std::uint16_t{0U}; options.vlan_priority = 4U; std::string error; REQUIRE(PtpPublisherRuntime::validate_options(options, error)); @@ -151,7 +151,7 @@ void check_priority_tagged_power_profile_wire() { REQUIRE(parsed.vlan_id == std::optional{0U}); REQUIRE(parsed.header.message_type == PtpMessageType::announce); source.stop(); - options.vlan_id = 4095U; + options.vlan_id = std::uint16_t{4095U}; REQUIRE(!PtpPublisherRuntime::validate_options(options, error)); } From ec54321c4b7022e4df784725166fce720e590522 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 22 Oct 2026 07:23:11 +0700 Subject: [PATCH 11/11] fix(test): avoid MSVC optional narrowing when asserting VID0 decoded value --- tests/test_ptp_power_profile.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_ptp_power_profile.cpp b/tests/test_ptp_power_profile.cpp index f78f8eb49..f1d10910d 100644 --- a/tests/test_ptp_power_profile.cpp +++ b/tests/test_ptp_power_profile.cpp @@ -148,7 +148,8 @@ void check_priority_tagged_power_profile_wire() { REQUIRE(raw[16] == 0x88U && raw[17] == 0xF7U); PtpFrame parsed{}; REQUIRE(PtpCodec::try_parse_ethernet_frame(raw, parsed)); - REQUIRE(parsed.vlan_id == std::optional{0U}); + REQUIRE(parsed.vlan_id.has_value()); + REQUIRE(*parsed.vlan_id == static_cast(0U)); REQUIRE(parsed.header.message_type == PtpMessageType::announce); source.stop(); options.vlan_id = std::uint16_t{4095U};