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' 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 { diff --git a/apps/arstack_studio/src/DeviceController.cpp b/apps/arstack_studio/src/DeviceController.cpp index 94dbf721a..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 || - (profile.value(QStringLiteral("vlanEnabled")).toBool() && vlan == 0U)) { + announce < 100U || sync < 20U) { return {}; } return QStringLiteral("PTP CONFIG %1 %2 %3 %4 %5 %6 %7 %8") 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 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 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) { diff --git a/tests/test_ptp_power_profile.cpp b/tests/test_ptp_power_profile.cpp index f76527391..f1d10910d 100644 --- a/tests/test_ptp_power_profile.cpp +++ b/tests/test_ptp_power_profile.cpp @@ -131,6 +131,31 @@ 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 = std::uint16_t{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.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}; + 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 @@ -309,6 +334,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";