Skip to content

P3-A1: native ARStack Studio binary profile deployment and installer candidate #675

P3-A1: native ARStack Studio binary profile deployment and installer candidate

P3-A1: native ARStack Studio binary profile deployment and installer candidate #675

name: ESP32-P4 PTP Lab Profile
on:
push:
paths:
- 'embedded/esp32p4_smv_injector/**'
- 'include/ariec61850/time_sync/**'
- 'src/time_sync/**'
- 'include/ariec61850/ethernet/**'
- 'src/ethernet/**'
- '.github/workflows/esp32p4-ptp-lab.yml'
pull_request:
paths:
- 'embedded/esp32p4_smv_injector/**'
- 'include/ariec61850/time_sync/**'
- 'src/time_sync/**'
- 'include/ariec61850/ethernet/**'
- 'src/ethernet/**'
- '.github/workflows/esp32p4-ptp-lab.yml'
permissions:
contents: read
concurrency:
group: esp32p4-ptp-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build-esp32p4-ptp:
strategy:
fail-fast: false
matrix:
include:
- role_name: source
role_value: 0
- role_name: receiver
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:
run:
shell: bash
working-directory: embedded/esp32p4_smv_injector
steps:
- uses: actions/checkout@v4
- 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: |
set_config() {
local key="$1"
local value="$2"
if grep -q "^${key}=" sdkconfig; then
sed -i "s|^${key}=.*|${key}=${value}|" sdkconfig
elif grep -q "^# ${key} is not set$" sdkconfig && [ "$value" = "y" ]; then
sed -i "s|^# ${key} is not set$|${key}=y|" sdkconfig
else
echo "${key}=${value}" >> sdkconfig
fi
}
set_config CONFIG_AR_PTP_LAB_TX y
set_config CONFIG_AR_PTP_OPERATING_ROLE ${{ matrix.role_value }}
set_config CONFIG_AR_PTP_DOMAIN 0
set_config CONFIG_AR_PTP_TRANSPORT_SPECIFIC 1
set_config CONFIG_AR_PTP_PORT_NUMBER 2
set_config CONFIG_AR_PTP_VLAN y
set_config CONFIG_AR_PTP_VLAN_ID 100
set_config CONFIG_AR_PTP_VLAN_PRIORITY 4
set_config CONFIG_AR_PTP_ANNOUNCE_INTERVAL_MS 1000
set_config CONFIG_AR_PTP_SYNC_INTERVAL_MS 250
set_config CONFIG_AR_PTP_RESPOND_PDELAY y
set_config CONFIG_AR_PTP_PDELAY_REQUEST_INTERVAL_MS 1000
set_config CONFIG_AR_PTP_PRIORITY1 120
set_config CONFIG_AR_PTP_PRIORITY2 121
set_config CONFIG_AR_PTP_CLOCK_CLASS 248
set_config CONFIG_AR_PTP_CLOCK_ACCURACY 254
set_config CONFIG_AR_PTP_OFFSET_SCALED_LOG_VARIANCE 65535
set_config CONFIG_AR_PTP_TIME_SOURCE 160
set_config CONFIG_AR_PTP_CURRENT_UTC_OFFSET 37
set_config CONFIG_AR_PTP_MAX_PATH_DELAY_NS 5000000
set_config CONFIG_AR_PTP_MAX_PATH_JITTER_NS 100000
set_config CONFIG_AR_PTP_LOCK_OFFSET_NS 2000
set_config CONFIG_AR_PTP_UNLOCK_OFFSET_NS 20000
set_config CONFIG_AR_PTP_PHASE_STEP_THRESHOLD_NS 1000000
set_config CONFIG_AR_PTP_LOCK_REQUIRED_SAMPLES 8
set_config CONFIG_AR_PTP_SYNC_TIMEOUT_MS 1000
set_config CONFIG_AR_PTP_HOLDOVER_TIMEOUT_MS 5000
set_config CONFIG_AR_PTP_MAX_FREQ_ADJ_PPB 100000
source /opt/esp/idf/export.sh
idf.py reconfigure
grep -E '^CONFIG_AR_PTP_(LAB_TX|OPERATING_ROLE|DOMAIN|TRANSPORT_SPECIFIC|PORT_NUMBER|VLAN|VLAN_ID|VLAN_PRIORITY|PDELAY_REQUEST_INTERVAL_MS|LOCK_OFFSET_NS|UNLOCK_OFFSET_NS|LOCK_REQUIRED_SAMPLES|MAX_FREQ_ADJ_PPB)=' sdkconfig
grep -q '^CONFIG_AR_PTP_LAB_TX=y$' sdkconfig
grep -q '^CONFIG_AR_PTP_OPERATING_ROLE=${{ matrix.role_value }}$' sdkconfig
grep -q '^CONFIG_AR_PTP_TRANSPORT_SPECIFIC=1$' sdkconfig
grep -q '^CONFIG_AR_PTP_PORT_NUMBER=2$' sdkconfig
grep -q '^CONFIG_AR_PTP_VLAN_ID=100$' sdkconfig
grep -q '^CONFIG_AR_PTP_LOCK_OFFSET_NS=2000$' sdkconfig
grep -q '^CONFIG_AR_PTP_LOCK_REQUIRED_SAMPLES=8$' sdkconfig
- name: Build PTP-P2 firmware
run: source /opt/esp/idf/export.sh && idf.py build