Repository navigation
fix(esp32p4): eliminate START timing telemetry blocker #691
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: ESP32-P4 SMV Injector | |
| on: | |
| workflow_call: | |
| push: | |
| paths: | |
| - 'embedded/esp32p4_smv_injector/**' | |
| - 'include/ariec61850/asn1/**' | |
| - 'include/ariec61850/ethernet/**' | |
| - 'include/ariec61850/mms/utc_time.hpp' | |
| - 'include/ariec61850/sampled_values/**' | |
| - 'src/asn1/**' | |
| - 'src/ethernet/**' | |
| - 'src/mms/utc_time.cpp' | |
| - 'src/sampled_values/**' | |
| - '.github/workflows/esp32p4-smv-injector.yml' | |
| pull_request: | |
| paths: | |
| - 'embedded/esp32p4_smv_injector/**' | |
| - 'include/ariec61850/asn1/**' | |
| - 'include/ariec61850/ethernet/**' | |
| - 'include/ariec61850/mms/utc_time.hpp' | |
| - 'include/ariec61850/sampled_values/**' | |
| - 'src/asn1/**' | |
| - 'src/ethernet/**' | |
| - 'src/mms/utc_time.cpp' | |
| - 'src/sampled_values/**' | |
| - '.github/workflows/esp32p4-smv-injector.yml' | |
| permissions: | |
| contents: read | |
| env: | |
| ARSTACK_FIRMWARE_VERSION: '0.1.0' | |
| ARSTACK_PROTOCOL_VERSION: '1' | |
| jobs: | |
| build-esp32p4: | |
| runs-on: ubuntu-latest | |
| 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 P0 pre-v3 silicon policy | |
| run: | | |
| set -euo pipefail | |
| grep -qx 'CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y' sdkconfig | |
| echo 'ESP32-P4 pre-v3 build policy: PASS' | |
| - name: Verify realtime START and telemetry boundary policy | |
| run: | | |
| set -euo pipefail | |
| python3 - <<'PY' | |
| from pathlib import Path | |
| source = Path('main/app_main.cpp').read_text(encoding='utf-8') | |
| start_begin = source.index('if (take_start_request())') | |
| start_end = source.index('if (!live_tx_running())', start_begin) | |
| start_block = source[start_begin:start_end] | |
| clock_start = start_block.rindex( | |
| 'sample_clock = start_sample_clock(task_handle, active_profile.publisher_rate_hz);' | |
| ) | |
| assert start_block.rindex('ESP_LOGI') < clock_start | |
| assert 'ESP_LOG' not in start_block[clock_start:] | |
| assert 'static_assert(std::atomic<std::uint32_t>::is_always_lock_free);' in source | |
| assert 'g_sample_tick_total.fetch_add(1U, std::memory_order_relaxed);' in source | |
| assert 'elapsed_ticks) * 1000000ULL' in source | |
| assert 'rate_baseline_valid = true;' in source | |
| print('Realtime boundary policy: PASS - timer starts after formatting and telemetry rate is elapsed-time normalized') | |
| PY | |
| - name: Build firmware | |
| run: source /opt/esp/idf/export.sh && idf.py build | |
| - name: Create single-image recovery firmware | |
| run: | | |
| set -euo pipefail | |
| source /opt/esp/idf/export.sh | |
| image="arstack-esp32p4-smv-${ARSTACK_FIRMWARE_VERSION}.bin" | |
| # idf.py runs esptool's merge_bin from inside the build directory. | |
| # Passing build/<name> therefore resolves to build/build/<name> and fails. | |
| idf.py merge-bin -o "${image}" -f raw | |
| test -s "build/${image}" | |
| - name: Create release manifest and SHA-256 contract | |
| run: | | |
| set -euo pipefail | |
| image="arstack-esp32p4-smv-${ARSTACK_FIRMWARE_VERSION}.bin" | |
| sha="$(sha256sum "build/${image}" | awk '{print $1}')" | |
| python3 - "$image" "$sha" "$GITHUB_SHA" <<'PY' | |
| import json | |
| import os | |
| import sys | |
| image, sha256, source_commit = sys.argv[1:] | |
| manifest = { | |
| "schema": "arstack.studio.firmware.v1", | |
| "product": "ARStack ESP32-P4 SMV Injector", | |
| "chip": "esp32p4", | |
| "chipRevisionPolicy": "pre-v3", | |
| "version": os.environ["ARSTACK_FIRMWARE_VERSION"], | |
| "protocol": int(os.environ["ARSTACK_PROTOCOL_VERSION"]), | |
| "capabilities": ["SMV-4I4V", "PROFILE", "LIVE-SETPOINTS"], | |
| "flashOffset": 0, | |
| "image": image, | |
| "sha256": sha256, | |
| "espIdf": "5.5.4", | |
| "sourceCommit": source_commit, | |
| } | |
| with open("build/firmware-manifest.json", "w", encoding="utf-8", newline="\n") as handle: | |
| json.dump(manifest, handle, indent=2, sort_keys=True) | |
| handle.write("\n") | |
| PY | |
| python3 - <<'PY' | |
| import hashlib, json, pathlib | |
| root = pathlib.Path('build') | |
| manifest = json.loads((root / 'firmware-manifest.json').read_text(encoding='utf-8')) | |
| payload = (root / manifest['image']).read_bytes() | |
| actual = hashlib.sha256(payload).hexdigest() | |
| assert manifest['schema'] == 'arstack.studio.firmware.v1' | |
| assert manifest['chip'] == 'esp32p4' | |
| assert manifest['chipRevisionPolicy'] == 'pre-v3' | |
| assert manifest['flashOffset'] == 0 | |
| assert manifest['protocol'] == 1 | |
| assert actual == manifest['sha256'] | |
| print(f"Firmware package: PASS · {manifest['image']} · pre-v3 · SHA-256 {actual}") | |
| PY | |
| - name: Stage Studio firmware package | |
| run: | | |
| set -euo pipefail | |
| mkdir -p build/studio-firmware | |
| cp "build/arstack-esp32p4-smv-${ARSTACK_FIRMWARE_VERSION}.bin" build/studio-firmware/ | |
| cp build/firmware-manifest.json build/studio-firmware/ | |
| sha256sum build/studio-firmware/* > build/studio-firmware/SHA256SUMS.txt | |
| - name: Upload ESP32-P4 firmware package | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: arstack-esp32p4-smv-firmware | |
| path: embedded/esp32p4_smv_injector/build/studio-firmware/ | |
| if-no-files-found: error | |
| retention-days: 14 |