From 41379d924bd0107a869045cdd432d3130d8d3dcd Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Mon, 10 Aug 2026 18:29:41 +0700 Subject: [PATCH] feat: ESP32-P4 realtime SV foundation and vendor-neutral architecture --- .github/workflows/esp32p4-smv-injector.yml | 43 ++ NORTH_STAR.md | 384 +++++++++++ PROJECT_HANDOFF.md | 294 ++++++++ README.md | 101 ++- docs/GLOBAL_BENCHMARK.md | 205 ++++++ embedded/esp32p4_smv_injector/.gitignore | 5 + embedded/esp32p4_smv_injector/ARCHITECTURE.md | 186 +++++ embedded/esp32p4_smv_injector/CMakeLists.txt | 4 + embedded/esp32p4_smv_injector/README.md | 163 +++++ .../arstack_process_bus/CMakeLists.txt | 13 + .../esp32p4_smv_injector/main/CMakeLists.txt | 8 + .../main/Kconfig.projbuild | 68 ++ .../esp32p4_smv_injector/main/app_main.cpp | 641 ++++++++++++++++++ .../esp32p4_smv_injector/main/ethernet_port.c | 76 +++ .../esp32p4_smv_injector/main/ethernet_port.h | 23 + .../esp32p4_smv_injector/sdkconfig.defaults | 15 + 16 files changed, 2207 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/esp32p4-smv-injector.yml create mode 100644 NORTH_STAR.md create mode 100644 PROJECT_HANDOFF.md create mode 100644 docs/GLOBAL_BENCHMARK.md create mode 100644 embedded/esp32p4_smv_injector/.gitignore create mode 100644 embedded/esp32p4_smv_injector/ARCHITECTURE.md create mode 100644 embedded/esp32p4_smv_injector/CMakeLists.txt create mode 100644 embedded/esp32p4_smv_injector/README.md create mode 100644 embedded/esp32p4_smv_injector/components/arstack_process_bus/CMakeLists.txt create mode 100644 embedded/esp32p4_smv_injector/main/CMakeLists.txt create mode 100644 embedded/esp32p4_smv_injector/main/Kconfig.projbuild create mode 100644 embedded/esp32p4_smv_injector/main/app_main.cpp create mode 100644 embedded/esp32p4_smv_injector/main/ethernet_port.c create mode 100644 embedded/esp32p4_smv_injector/main/ethernet_port.h create mode 100644 embedded/esp32p4_smv_injector/sdkconfig.defaults diff --git a/.github/workflows/esp32p4-smv-injector.yml b/.github/workflows/esp32p4-smv-injector.yml new file mode 100644 index 000000000..836854433 --- /dev/null +++ b/.github/workflows/esp32p4-smv-injector.yml @@ -0,0 +1,43 @@ +name: ESP32-P4 SMV Injector + +on: + 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' + +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: Build firmware + run: source /opt/esp/idf/export.sh && idf.py build diff --git a/NORTH_STAR.md b/NORTH_STAR.md new file mode 100644 index 000000000..783996849 --- /dev/null +++ b/NORTH_STAR.md @@ -0,0 +1,384 @@ +# ARStack61850 North Star + +> **ARIEC61850 understands IEC 61850. ARStack61850 measures and executes IEC 61850 in real time.** + +ARStack61850 is the real-time, hardware-aware evolution of ARIEC61850. The goal is not merely to port protocol code to C++ or to build another desktop packet viewer. The long-term goal is an **open, evidence-driven IEC 61850 instrumentation platform** for reliable Sampled Values, GOOSE, PTP, commissioning, closed-loop testing, and process-bus measurement. + +The quality bar is defined by professional instrumentation principles rather than by imitation of any commercial product: dedicated measurement hardware, SCL-centric engineering, deterministic execution, hardware timestamping, explicit synchronization state, reproducible evidence, and standards-backed interoperability. + +## 1. Product thesis + +A normal desktop application sees packets after they have crossed a NIC, host transport, OS driver, capture framework, scheduler, and application runtime. That is useful for many engineering tasks, but it is not enough to make measurement-grade claims about process-bus timing. + +ARStack moves the timing-critical path into dedicated hardware: + +```text + ENGINEERING / CONTROL PLANE +┌──────────────────────────────────────────────────────────────────────┐ +│ Host application │ +│ │ +│ SCL engineering profile compiler waveform/fault editor │ +│ evidence/report automation long-term storage / PCAP │ +└──────────────────────────────┬───────────────────────────────────────┘ + │ versioned compiled configuration + │ high-speed isolated management link + v + REAL-TIME INSTRUMENT PLANE +┌──────────────────────────────────────────────────────────────────────┐ +│ ARStack hardware │ +│ │ +│ RTOS DMA-aware Ethernet hardware timestamps │ +│ PTP clock/servo deterministic SV/GOOSE bounded capture/trigger │ +│ sequence/quality timing statistics evidence metadata │ +└──────────────────────────────┬───────────────────────────────────────┘ + │ process / station bus + v + Relay / MU / IED +``` + +The host is the engineering brain and visualization surface. The hardware is the **time and packet truth sensor**. + +## 2. What makes ARStack different from ARIEC61850 + +ARIEC61850 remains valuable as a portable protocol and engineering reference. ARStack adds capabilities that must be designed around real-time hardware rather than ordinary application timing: + +- RTOS-native execution and explicit task/core ownership; +- fixed/preallocated hot paths with no uncontrolled heap allocation; +- Ethernet DMA/ring awareness and explicit overrun/drop accounting; +- hardware RX/TX timestamps rather than host-arrival timestamps; +- PTP-disciplined hardware time and explicit lock/holdover state; +- deterministic sample scheduling and deadline evidence; +- SCL-compiled immutable runtime profiles; +- process-bus-specific measurement, trigger, and evidence pipelines; +- closed-loop regression and reproducible benchmark artifacts. + +Protocol intelligence should be reused from ARIEC61850 and related ARStack components where appropriate. Timing truth must come from ARStack hardware. + +## 3. Industry design principles + +Publicly documented instrumentation and real-time test systems converge on several recurring principles. ARStack adopts the principles without embedding vendor or product references in the repository. + +### 3.1 Dedicated hardware matters + +Precision process-bus work benefits from dedicated network interfaces, hardware timestamping, deterministic compute, and isolation between the engineering host and the measured network. + +**ARStack implication:** precision mode must place timing/capture in dedicated hardware rather than treating the host OS as the measurement clock. + +### 3.2 SCL is the engineering source of truth + +Professional IEC 61850 workflows are configuration-centric. IEC 61850-6 exists to exchange interoperable system and IED configuration between engineering tools. + +**ARStack implication:** standards-facing publishers/subscribers must be SCL-driven. Hard-coded APPID/MAC/VLAN/dataset values are acceptable only for bring-up or explicitly named compatibility profiles. + +### 3.3 Process-bus analysis is more than packet decoding + +A serious analyzer must combine protocol semantics, sequence integrity, signal reconstruction, timing statistics, SCL comparison, redundancy awareness, triggers, and exportable evidence. + +### 3.4 Real-time testing must support abnormal conditions + +Controlled packet loss, delay, duplication, reordering, quality changes, synchronization loss, and waveform/fault scenarios should be first-class reproducible test features after the canonical deterministic path is proven. + +### 3.5 Evidence and conformance must remain separate + +A decoded frame or visually correct waveform is not a conformance certificate. + +**ARStack implication:** packet correctness, SCL match, timing evidence, synchronization evidence, redundancy behavior, relay interoperability, and formal conformance are independent gates. + +A vendor-neutral research summary is maintained in [`docs/GLOBAL_BENCHMARK.md`](docs/GLOBAL_BENCHMARK.md). + +## 4. Standards baseline + +ARStack must track standards explicitly rather than use “IEC 61850” as a generic label. + +| Area | Standards / references | ARStack role | +|---|---|---| +| Sampled Values mapping | IEC 61850-9-2 Ed.2.1 | Canonical L2 SV encode/decode and field-presence behavior | +| Configuration / SCL | IEC 61850-6 Ed.2.2 | Host-side configuration source and interoperability contract | +| Time synchronization | IEC/IEEE 61850-9-3 | PTP power-profile validation and synchronization policy | +| Digital instrument transformer interface | IEC 61869-9 | Modern MU/digital-instrument-transformer profile family | +| SAMU context | IEC 61869-13 | Stand-alone merging-unit use cases | +| Conformance | IEC 61850-10 Ed.2.1 | Test/evidence methodology and claim discipline | +| Relay functional interoperability | IEC TS 60255-216-1 | Protection functions using SV/GOOSE/time sync | +| Redundancy | IEC 62439-3 | PRP/HSR observation, publication and validation roadmap | +| IEC 61850 security | IEC 62351-6 | Security-aware future protocol/tooling boundary | + +Profile labels are not interchangeable: + +1. **Generic IEC 61850-9-2 / SCL-driven** — exact selected SCL stream drives the output. +2. **IEC 61869-9** — only when its constraints are explicitly implemented and evidenced. +3. **Legacy 9-2LE compatibility** — an explicit compatibility mode, never silently promoted to the generic standard. + +## 5. Core architecture principles + +### 5.1 Separate facts, expectations, claims and synchronization + +- **wire facts** — what was actually received/transmitted/timestamped; +- **configured expectations** — what SCL/profile says should happen; +- **profile claims** — what compatibility/conformance conclusion is justified; +- **synchronization evidence** — what is known about PTP lock and clock quality. + +Unknown stays unknown. A heuristic must never silently become a standards claim. + +### 5.2 Keep SCL/XML out of the realtime hot path + +The host parses SCD/CID/ICD/IID and compiles a compact immutable profile. The embedded device validates version/length/checksum, builds fixed packet templates, then executes them deterministically. + +```text +SvPublisherProfile + schemaVersion + profileFamily + destinationMac + appId + vlanId / vlanPriority + svId + confRev + nofAsdu + SmvOpts field-presence policy + dataSetReference + samplingBasis / sampleRate + sampleCounterPolicy + synchronizationPolicy + channels[] + semantic + basicType + byteOffset + scale + qualityPolicy +``` + +### 5.3 One hot path, no surprise work + +In deterministic mode: + +- no XML parsing; +- no string formatting; +- no logging per packet; +- no unbounded queues; +- no hidden heap allocation; +- no dynamic frame re-encoding when a fixed template can be patched; +- no dependency on desktop scheduling for sample cadence. + +### 5.4 Measurement timestamp hierarchy + +Never collapse these timestamps into one metric: + +```text +T0 scheduled sample deadline +T1 realtime publisher wake / ready time +T2 EMAC hardware TX timestamp +T3 ARStack analyzer hardware RX timestamp +T4 external reference capture timestamp +T5 host/OS capture arrival timestamp +``` + +`T5` is useful for capture-path diagnostics but is not device-TX timing truth. + +### 5.5 `smpSynch` must be truthful + +```text +no valid synchronization evidence -> unsynchronized +acquiring / unlocked -> unsynchronized +measured valid local sync -> profile-defined local indication +measured valid global/PTP lock -> profile-defined global indication +holdover -> explicit policy + evidence +``` + +Compatibility overrides may exist for relay-readability tests, but they must be visibly labeled and must never be reported as synchronization evidence. + +## 6. Product family north star + +### 6.1 ARStack SV Injector + +Target capabilities: + +- SCL stream import and exact profile compilation; +- 50/60 Hz and profile-defined sampling; +- 4I+4V and arbitrary SCL-bound datasets; +- waveform/fault/state sequencer; +- COMTRADE playback; +- quality/test/simulation bit manipulation; +- controlled packet loss, delay, duplication, reordering and sync-loss scenarios; +- deterministic multi-stream publishing; +- hardware TX timestamp statistics; +- PTP-disciplined publishing; +- reusable relay-oriented test scenarios; +- machine-readable evidence export. + +### 6.2 ARStack Process Bus Analyzer + +Target capabilities: + +- passive SV/GOOSE/PTP discovery; +- per-stream hardware RX timestamps; +- packet interval/jitter histograms and P50/P95/P99/max; +- sequence gap, duplicate and out-of-order detection; +- waveform, RMS, frequency, phasor and harmonics; +- SCL expected-vs-observed comparison; +- quality/test/simulation diagnostics; +- PTP source/domain/offset/path-delay/lock diagnostics; +- PRP/HSR duplicate-path and asymmetry analysis; +- triggers and bounded pre/post-event capture; +- PCAP/COMTRADE/evidence reports; +- cross-device time-correlated capture. + +### 6.3 ARStack Digital Substation Probe + +- portable or DIN-rail operation; +- isolated management plane; +- continuous health/event monitoring; +- remote evidence capture; +- station/process-bus diagnostics without making the engineering host the measurement endpoint. + +### 6.4 ARStack Real-Time Test Runtime + +A longer-term deterministic environment for multi-IED tests, abnormal traffic injection, merging-unit simulation, and closed-loop protection testing. + +## 7. Hardware roadmap + +### Stage A — current ESP32-P4 development platform + +Purpose: prove the real-time architecture cheaply and openly. + +- dual-core RTOS runtime; +- internal EMAC + RMII PHY; +- IEEE1588v2-capable EMAC; +- USB control/debug; +- one process-bus Ethernet port; +- suitable for injector and mirrored/TAP-output analyzer development. + +The current one-port development board is **not** the final answer for a transparent inline two-port measurement instrument. + +### Stage B — ARStack Precision hardware + +Recommended direction: + +- at least two independent process-side network ports; +- copper + optical options where practical; +- hardware timestamp at ingress/egress as close to the MAC/PHY as possible; +- dedicated oscillator / PTP-disciplined clock domain; +- optional programmable logic or deterministic switch/TAP front-end for transparent forwarding, duplication, line-rate filtering and multi-port timestamping; +- real-time processor for RTOS/control/protocol execution; +- high-speed isolated management link to host; +- process network electrically/logically separated from the engineering host; +- local nonvolatile evidence buffer; +- secure boot/update path. + +### Stage C — redundancy / multi-network instrument + +- dual-LAN PRP; +- HSR-aware capture/injection; +- multiple independent station/process networks; +- multi-device clock correlation; +- hardware acceleration when MCU-only execution no longer provides adequate margin. + +## 8. Evidence-first quality model + +Minimum evidence bundle for a deterministic SV run: + +```text +profile.json +runtime-config.json +firmware build/version/commit +hardware identity/revision +PTP/clock state +per-second counters +scheduled-vs-TX hardware timing statistics +RX/reference timing statistics when available +sequence integrity summary +PCAP/PCAPNG +optional COMTRADE +machine-readable verdict + human report +``` + +A future hardware CI bench should be able to reject a build automatically when thresholds regress, for example: + +- unexplained `smpCnt` gap; +- TX/RX queue overflow; +- missed realtime deadline; +- P99 hardware timing error above project threshold; +- PTP unlock during a locked test; +- SCL/profile mismatch; +- wrong APPID/VLAN/MAC/dataset ordering; +- PRP/HSR path asymmetry outside test policy. + +Thresholds must be evidence-based and profile-specific, not invented globally. + +## 9. Current proven state + +Current bench observations have demonstrated: + +- ESP32-P4 rev 1.3 + RMII PHY link bring-up; +- raw Ethernet transmission to a host capture path; +- IEC 61850 Sampled Values frames decoded by an independent packet capture stack; +- GPTimer + FreeRTOS notification scheduling at 4000 samples/s for 50 Hz / 80 samples-per-cycle; +- zero device-side TX failures and zero missed timer notifications in the observed stable runs; +- balanced reconstructed 50 Hz three-phase waveform on the diagnostic stream; +- continuous sequence integrity in the analyzer; +- clear evidence that a host USB-Ethernet capture path can distort/batch individual arrival timestamps even while average throughput remains about 4000 fps. + +These are **bench observations, not formal conformance evidence**. + +Current limitations: + +- canonical multicast/VLAN capture needs validation on a trusted measurement path; +- hardware TX timestamps are not yet wired into the ARStack evidence loop; +- PTP clock discipline/61850-9-3 is not implemented yet; +- SCL-driven compiled profiles are not yet deployed to the ESP runtime; +- 60 Hz / 4800 samples/s evidence is still pending; +- multi-stream and PRP/HSR are future gates; +- engineering scaling/profile semantics still require standards-backed profile work. + +See [`PROJECT_HANDOFF.md`](PROJECT_HANDOFF.md), issue #21, issue #24 and the current embedded PR for active implementation state. + +## 10. Development gates + +### Gate P2 — deterministic single-stream evidence + +- sustained 4000 fps then 4800 fps; +- zero unexplained sequence gaps; +- bounded queue behavior; +- EMAC hardware TX timestamp integration; +- 60 s evidence runs; +- independent capture/reference evidence. + +### Gate P3 — standards-first profiles + PTP + +- host SCL -> immutable `SvPublisherProfile`; +- exact field-presence policy from SCL/selected profile; +- profile validation/golden frames; +- hardware PTP clock enable/read/adjust; +- passive PTP monitor + IEC/IEEE 61850-9-3 validator; +- servo state machine (unlocked/acquiring/locked/holdover); +- `smpSynch` driven by measured state; +- profile + timing evidence export. + +### Gate P4 — professional injector/analyzer workflow + +- fault/scenario engine and COMTRADE playback; +- hardware-timestamped receive analyzer; +- trigger engine; +- SCL expected-vs-observed diagnostics; +- reusable test procedures and automated evidence reports. + +### Gate P5 — precision hardware + +- dual-port / multi-port front-end; +- transparent or TAP-style measurement path; +- stronger oscillator and timestamp architecture; +- PRP/HSR and multi-network support; +- independently validated measurement uncertainty and interoperability. + +## 11. Decision rule + +When architecture choices conflict, prefer: + +```text +standards truth + > measured hardware evidence + > deterministic architecture + > interoperability evidence + > usability + > convenience hacks +``` + +This file is the project constitution. New features should strengthen that hierarchy, not bypass it. \ No newline at end of file diff --git a/PROJECT_HANDOFF.md b/PROJECT_HANDOFF.md new file mode 100644 index 000000000..6df09a917 --- /dev/null +++ b/PROJECT_HANDOFF.md @@ -0,0 +1,294 @@ +# ARStack61850 Project Handoff + +This file is the **current-state handoff** for a new engineer or AI thread. Read [`NORTH_STAR.md`](NORTH_STAR.md) first for the long-term direction, then use this file to continue active development without reconstructing the bench history from chat. + +Last major bench update: **2026-08-10**. + +## 1. Active repository work + +- Repository: `masarray/arstack61850` +- Active branch: `feature/esp32p4-smv-injector-p0-p1` +- Active PR: **#19 — Add ESP32-P4-ETH SMV injector P0/P1 and P2 50 Hz realtime foundation** +- Timing evidence issue: **#21** +- Standards-first SCL/profile/PTP roadmap: **#24** + +Do not assume PR #19 is merged. Check its current head and CI before branching new embedded work. + +## 2. Tested hardware/toolchain + +### Device + +- ESP32-P4 Ethernet development board +- ESP32-P4 detected silicon revision: **v1.3** +- onboard RMII PHY +- physical interface: RMII +- board flash physically detected as 32 MB, while the current development image header is still configured smaller; this is not blocking current P2 work but should be normalized later. + +### Toolchain + +- tested: **ESP-IDF v5.5.5** +- early ESP32-P4 silicon requires the pre-v3 revision configuration; do not flash a v3.1-minimum image with `--force`. +- local `sdkconfig` on the tested machine contains the early-silicon setting and should not be casually deleted. + +### Host capture bench + +- USB 2.0 Fast Ethernet capture adapter +- host capture successfully receives broadcast diagnostic traffic. +- this host adapter/path has not reliably exposed the canonical multicast + VLAN process-bus stream and introduces substantial arrival-timestamp excursions/batching at ~4000 fps. +- therefore **host USB-Ethernet capture timestamps are not device timing truth**. + +## 3. Proven ESP32-P4 bring-up + +### P0 — physical/data-plane path: PASS for current bench + +Observed on real hardware: + +- ESP32-P4 boots firmware; +- onboard RMII PHY initializes; +- Ethernet link reaches `UP`; +- raw frames emitted by ESP reach host capture; +- diagnostic EtherType `0x88B5` probes are visible in packet capture; +- IEC 61850 `0x88BA` frames are visible through the diagnostic broadcast path. + +### P1 — IEC 61850 SV encode/decode: PASS for bench foundation + +Observed: + +- emitted frames are independently recognized as IEC 61850 Sampled Values; +- a raw process-bus analyzer decodes `svID`, APPID, `confRev`, `smpCnt` and 4I+4V payload shape; +- sequence continuity works when the diagnostic stream uses its own identity/counter policy. + +This is **interoperability evidence**, not a conformance certificate. + +## 4. Current P2 realtime publisher + +Implementation path: + +`embedded/esp32p4_smv_injector/main/app_main.cpp` + +Current architecture: + +- 50 Hz nominal; +- 80 samples/cycle; +- 4000 publisher ticks/s; +- 250 us period; +- ESP-IDF GPTimer at 1 MHz; +- GPTimer ISR performs only a FreeRTOS task notification; +- dedicated high-priority publisher task on CPU1 blocks between timer events; +- telemetry task is separate; +- fixed/prebuilt packet templates; +- runtime patches fixed-width `smpCnt`, `confRev` and `seqData`; +- no BER re-encode / heap allocation in the hot per-sample TX path; +- balanced A/B/C sine table; +- dataset shape: `Ia, Ib, Ic, In, Ua, Ub, Uc, Un`, each value followed by quality. + +A previous busy-spin scheduler caused Task WDT / `IDLE1` starvation. It was removed. **Do not reintroduce busy-spin timing as the primary cadence.** + +### Latest stable ESP-side observation + +Serial telemetry in the stable run showed repeated intervals approximately like: + +```text +P2 timing: samples=4000 (~4000 fps) +MC ok=4000 fail=0 +mirror ok=4000 fail=0 +missed=0 +wake_late_us[min/mean/max]=0/0/0 +``` + +Treat `wake_late_us` as scheduler/task evidence only. It is not yet EMAC wire-time evidence. + +## 5. Canonical stream vs diagnostic stream + +The two streams are intentionally separate. Never merge their identities. + +### Canonical standards-facing stream + +Aligned to the current sample SCL stream used on the bench: + +```text +destination 01:0C:CD:04:00:01 +APPID 0x4000 +VLAN PCP 4 / VID 0 +svID MU01_SV1 +confRev 1 +nofASDU 1 +rate 4000 samples/s (50 Hz x 80 samples/cycle) +dataset Ia Ib Ic In Ua Ub Uc Un + qualities +smpSynch 0 (truthful: no measured PTP/1PPS lock yet) +``` + +For the supplied SCL, canonical ASDU field presence currently follows its `SmvOpts`: dataset reference and explicit sample-rate fields are omitted where configuration says those optional fields are not carried. + +### Lab diagnostic stream + +Purpose: work around the current host-capture limitation so signal/timing-analysis UI can be exercised while preserving canonical traffic separately. + +```text +destination FF:FF:FF:FF:FF:FF +APPID 0x4F01 +svID AR_DIAG_SV1 +VLAN none +smpRate 4000 +smpMod SmpPerSec +smpCnt independent normal 16-bit counter +``` + +**The diagnostic stream is not the compliance target.** Do not modify canonical standards behavior merely to satisfy diagnostic-stream UI assumptions. + +## 6. Latest analyzer observation + +A raw process-bus analyzer reconstructed the diagnostic stream into: + +- clear 50 Hz voltage and current waveforms; +- balanced three-phase phasors about 120 degrees apart; +- ~4000 samples/s; +- continuous sequence / no missing samples in the stable observation window. + +The analyzer also reported arrival-time excursions while device-side sequence remained continuous and average throughput stayed near 4000 fps. + +Interpretation: **capture/timestamp path suspect** until hardware timestamps prove otherwise. Do not tune the ESP scheduler to mimic or cancel host USB-Ethernet batching. + +### Current engineering-value caveat + +The publisher currently uses a bench scaling convention (current 1 mA/count, voltage 10 mV/count). A raw analyzer that treats integer counts directly as A/V will therefore display values such as roughly 1000 A when the intended bench signal is 1 A. + +Do not “fix” this by corrupting the canonical payload to satisfy one raw viewer. Scaling and semantic interpretation must move into the standards/profile layer and expected-vs-observed model. + +## 7. Immediate next technical priorities + +### Priority A — EMAC IEEE1588 hardware timing evidence + +The ESP32-P4 Ethernet MAC exposes IEEE1588v2-capable hardware time through the pinned toolchain. + +Next work should: + +1. enable PTP hardware timestamp capability; +2. build an ARStack hardware-clock abstraction rather than leak version-sensitive low-level APIs everywhere; +3. correlate at least: + - `smpCnt`; + - scheduled sample deadline; + - publisher submit time; + - EMAC hardware TX time where obtainable; +4. calculate min/max/mean/P95/P99 error and missed deadlines; +5. run at least 60 s at 4000 fps; +6. repeat at 4800 fps / 60 Hz; +7. store machine-readable evidence. + +Do not use host USB-Ethernet timing as the pass/fail source for this gate. + +### Priority B — SCL -> compiled `SvPublisherProfile` + +ARStack already has SCL model/parser structures. Extend them instead of making a separate embedded XML parser. + +Desired path: + +```text +SCD/CID/ICD/IID + -> host SCL/profile compiler + -> validated versioned binary profile + -> management/control-plane deployment + -> ESP fixed runtime templates +``` + +The profile must preserve: + +- destination MAC; +- APPID; +- VLAN ID/PCP; +- svID; +- confRev; +- nofASDU; +- explicit optional-field presence (`SmvOpts`); +- dataset order/type/quality layout; +- sample-rate/sample-mode basis; +- sample-counter policy; +- engineering scaling policy; +- synchronization policy. + +Reject unresolved/ambiguous bindings. Do not guess. + +### Priority C — PTP / IEC/IEEE 61850-9-3 + +Use the evidence-oriented layering already present in related ARStack code, but make device timing hardware-based: + +1. PTP L2 codec/parser; +2. passive monitor; +3. Power Profile validator; +4. ESP32-P4 EMAC hardware clock; +5. peer/path-delay and offset estimation; +6. servo/discipline state; +7. explicit `Unlocked / Acquiring / Locked / Holdover` state; +8. `smpSynch` derived from measured state; +9. synchronization/timestamp evidence. + +### Priority D — analyzer hardware path + +After TX timestamping is understood, add receive-side instrumentation: + +- promiscuous process-bus RX; +- EMAC hardware RX timestamp metadata; +- bounded capture ring; +- per-stream sequence/jitter statistics; +- trigger engine; +- packet + metadata transport to host; +- compare hardware RX timing against host-capture timing. + +## 8. Longer-term hardware direction + +The current single-port ESP32-P4 board is a development platform, not the final precision-instrument form. + +Future precision instrument should investigate: + +- two or more process-side ports; +- copper + optical options; +- passive/transparent TAP or deterministic switch/programmable-logic front-end; +- timestamping as close to ingress/egress as possible; +- PTP-disciplined oscillator/clock domain; +- PRP/HSR-aware duplicate paths; +- high-speed isolated management/control link; +- process-network isolation from the user host; +- secure boot/update and local evidence buffering. + +See [`NORTH_STAR.md`](NORTH_STAR.md) and [`docs/GLOBAL_BENCHMARK.md`](docs/GLOBAL_BENCHMARK.md). + +## 9. Build / flash commands for the current tested bench + +Use the ESP-IDF v5.5.5 environment. Preserve the local early-silicon `sdkconfig`. + +```powershell +cd C:\Git\arstack61850 +git checkout feature/esp32p4-smv-injector-p0-p1 +git pull --ff-only + +cd embedded\esp32p4_smv_injector +idf.py reconfigure +idf.py build +idf.py -p COM3 flash monitor +``` + +Exit monitor with `Ctrl+]`. + +If the port changes, discover the actual COM port instead of assuming COM3. + +## 10. Bench safety boundary + +- use an isolated lab network, direct cable, or proper TAP/SPAN path; +- do not attach development publisher traffic to an operational substation process bus; +- treat diagnostic broadcast traffic as lab-only; +- preserve truthful `smpSynch=0` until real synchronization is measured; +- do not claim strict 9-2LE/IEC 61869-9/IEC 61850 conformance until the appropriate profile and independent evidence gates pass. + +## 11. New-thread checklist + +Before changing code, a new thread should answer: + +- What exact branch/head/PR is active? +- Is the proposed change standards-facing, diagnostic-only, or bench-only? +- What timestamp layer is being measured (scheduler, HW TX, HW RX, reference capture, host capture)? +- Does the change preserve SCL/profile truth? +- Does it introduce allocation/logging/string/XML work into the hot path? +- Does it affect canonical traffic or only the diagnostic stream? +- What evidence will prove the change worked? + +If those questions cannot be answered, stop and inspect the current code/issues before editing. \ No newline at end of file diff --git a/README.md b/README.md index dadecb0e7..16caab86a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,41 @@ -# ARIEC61850 C++ Port +# ARStack61850 — Real-Time IEC 61850 Instrumentation Stack -Incremental C++20 migration of the ARIEC61850 C# protocol stack. The C# implementation -remains the behavioral oracle until cross-language comparison and isolated-laboratory -interoperability are complete. +ARStack61850 is the real-time, hardware-aware evolution of ARIEC61850. + +> **ARIEC61850 understands IEC 61850. ARStack61850 measures and executes IEC 61850 in real time.** + +The portable C++20 stack remains rooted in the ARIEC61850 behavioral model, but the project north star is now broader: build an open, deterministic IEC 61850 instrumentation platform for hardware-timestamped Sampled Values/GOOSE/PTP injection, analysis, commissioning and real-time closed-loop testing. + +## Start here + +For a new engineer or AI development thread, read these before making architecture changes: + +1. [`NORTH_STAR.md`](NORTH_STAR.md) — product thesis, standards, hardware roadmap and definition of success. +2. [`PROJECT_HANDOFF.md`](PROJECT_HANDOFF.md) — current ESP32-P4 bench status and immediate next work. +3. [`embedded/esp32p4_smv_injector/ARCHITECTURE.md`](embedded/esp32p4_smv_injector/ARCHITECTURE.md) — embedded real-time architecture boundaries. +4. [`docs/GLOBAL_BENCHMARK.md`](docs/GLOBAL_BENCHMARK.md) — vendor-neutral standards/industry research behind the direction. +5. Issue #21 — deterministic SV timing-evidence gate. +6. Issue #24 — standards-first SCL/profile/PTP roadmap. +7. PR #19 — current ESP32-P4 hardware implementation under bench validation. + +The project deliberately separates **wire facts**, **configured expectations**, **profile claims**, and **synchronization evidence**. A packet that decodes in a host capture tool is not, by itself, a conformance or timing claim. + +## Current hardware milestone + +The active ESP32-P4 work has demonstrated on a real development bench: + +- ESP32-P4 rev 1.3 + onboard RMII PHY bring-up; +- raw Ethernet TX reaching a host capture path; +- independently decoded IEC 61850 Sampled Values frames; +- GPTimer/FreeRTOS-driven 50 Hz / 80 samples-per-cycle publishing at about 4000 samples/s; +- stable observed runs with zero device-side TX failures and zero missed GPTimer notifications; +- balanced three-phase waveform reconstruction on a separate lab diagnostic stream. + +The next timing gate is **ESP32-P4 EMAC IEEE1588v2 hardware timestamp evidence**, not tuning the publisher against host USB-Ethernet arrival jitter. + +## Lineage and portable core + +The original work began as an incremental C++20 migration of the ARIEC61850 C# protocol stack. The C# implementation remains a behavioral oracle where cross-language equivalence is still useful, while ARStack-specific work adds deterministic RTOS/hardware execution and evidence that a normal desktop stack cannot provide. ## Implemented @@ -17,8 +50,7 @@ interoperability are complete. - IEC 61850 UTC time and recursive MMS Data/AllData codec. - Complete GOOSE PDU/frame codecs and offline publisher/subscriber supervision. -- Sampled Values ASDU, multi-ASDU PDU, Ethernet frame, payload, quality, counter, and - stream supervision. +- Sampled Values ASDU, multi-ASDU PDU, Ethernet frame, payload, quality, counter, and stream supervision. - C#-derived synthetic PCAP equivalence evidence. - Sanitizer, deterministic mutation-smoke, and LLVM libFuzzer coverage. - Receive-only isolated-laboratory tooling. @@ -27,8 +59,7 @@ interoperability are complete. - Bounded, dependency-free XML/SCL reader. - IED, DataSet, FCDA, GOOSE, Sampled Values, report-control, and communication mapping. -- SCL edition detection, type-template resolution, DataSet binding, diagnostics, and - SCL/XML fuzzing. +- SCL edition detection, type-template resolution, DataSet binding, diagnostics, and SCL/XML fuzzing. ### Phase 2B — COMTRADE foundation @@ -77,23 +108,32 @@ interoperability are complete. - Sequence, configuration, DataSet, overflow, and segmentation continuity tracking. - Bounded offline report monitor and dedicated MMS-reporting fuzz corpus. -No enabled runtime opens TCP sockets, host packet-capture interfaces, raw sockets, or a network interface. Physical IED -interoperability and all active transmission remain gated. +### Hardware process-bus runtime — ESP32-P4 P0/P1/P2 foundation + +The portable core still does not silently open raw sockets or physical IED sessions. **Active process-bus transmission exists only in explicitly enabled hardware/lab targets**, currently `embedded/esp32p4_smv_injector`, and is intended for isolated bench use. + +The current ESP32-P4 target adds: + +- RMII hardware bring-up; +- active Layer-2 SV TX; +- fixed/prebuilt runtime templates; +- deterministic GPTimer cadence; +- separate canonical vs diagnostic stream identities; +- 4I+4V balanced waveform generation; +- once-per-second realtime telemetry; +- a path toward EMAC hardware timestamps and PTP-disciplined synchronization. + +See the embedded README/architecture files and `PROJECT_HANDOFF.md` for the exact current state. ## Phase 4B: association and report-subscription runtime -The C++ core now includes a transport-injected MMS association runtime and persistent RCB -subscription state machine. It performs COTP, Session, Presentation, ACSE/MMS Initiate, -confirmed-service routing, timeout/cancellation handling, RCB re-probe, URCB reservation, -`RptEna`, optional GI, InformationReport delivery, and ownership-aware cleanup. See -`ASSOCIATION_RUNTIME_PROFILE.md`. +The C++ core now includes a transport-injected MMS association runtime and persistent RCB subscription state machine. It performs COTP, Session, Presentation, ACSE/MMS Initiate, confirmed-service routing, timeout/cancellation handling, RCB re-probe, URCB reservation, `RptEna`, optional GI, InformationReport delivery, and ownership-aware cleanup. See `ASSOCIATION_RUNTIME_PROFILE.md`. -The core deliberately does not ship a default socket transport in this phase. Applications -that intentionally enable live IED access must provide an `MmsByteTransport` implementation. +The core deliberately does not ship a default socket transport in this phase. Applications that intentionally enable live IED access must provide an `MmsByteTransport` implementation. ## Build -### Windows — Visual Studio +### Windows ```powershell cmake -S . -B build -A x64 -DARIEC61850_WARNINGS_AS_ERRORS=ON @@ -101,7 +141,7 @@ cmake --build build --config Release --parallel ctest --test-dir build -C Release --output-on-failure ``` -### Linux — GCC or Clang +### Linux ```bash cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DARIEC61850_WARNINGS_AS_ERRORS=ON @@ -133,6 +173,23 @@ ctest --test-dir build-sanitizers --output-on-failure .\scripts\run-lab-check.ps1 -Pcap .\captures\device-session.pcap ``` -See `REPORTING_PROFILE.md` for the offline report decoder profile, -`LAB_INTEROP_CHECKLIST.md` before using a physical IED, and -`MIGRATION_CHECKLIST.md` for the detailed progress ledger. +See `REPORTING_PROFILE.md` for the offline report decoder profile, `LAB_INTEROP_CHECKLIST.md` before using a physical IED, and `MIGRATION_CHECKLIST.md` for the detailed progress ledger. + +## North-star reminder + +ARStack is not intended to become a UI-heavy packet viewer with timing bolted on later. The architecture priority is: + +```text +standards truth + > measured hardware evidence + > deterministic execution + > interoperability evidence + > usability + > convenience hacks +``` + +The long-term product family — injector, process-bus analyzer, digital-substation probe and real-time MU/closed-loop simulator — should share the same real-time/evidence core. + +## Public-repository naming policy + +Public project documentation should remain vendor-neutral. Do not add commercial competitor names, product comparisons, logos, screenshots, or vendor marketing references. Standards, protocol identifiers, and technical platform/dependency names required to build or reproduce the project are allowed. \ No newline at end of file diff --git a/docs/GLOBAL_BENCHMARK.md b/docs/GLOBAL_BENCHMARK.md new file mode 100644 index 000000000..24d919b67 --- /dev/null +++ b/docs/GLOBAL_BENCHMARK.md @@ -0,0 +1,205 @@ +# Global benchmark: vendor-neutral IEC 61850 instrumentation principles + +This note records the public standards and industry design lessons used to shape the ARStack61850 north star. The repository intentionally avoids naming or comparing against commercial brands/products. The goal is to preserve useful architectural lessons without creating a competitor-reference document. + +Research snapshot: **August 2026**. + +## 1. Industry benchmark principles + +Publicly documented high-end digital-substation instruments and real-time test platforms repeatedly converge on the same architectural patterns: + +| Capability class | Recurring industry pattern | ARStack lesson | +|---|---|---| +| Dedicated measurement hardware | timing and capture are moved out of a normal engineering PC; network and management planes are isolated | the host should not be the timing instrument | +| SCL-centric engineering | system configuration drives stream identity, mapping, expectations and tests | standards-facing workflows must be SCL-driven | +| Process-bus analysis | SV/GOOSE/PTP, signal reconstruction, sequence integrity, timing, redundancy and recording are analyzed together | packet decode alone is not enough | +| Deterministic real-time execution | time-critical publishing/subscribing is isolated from ordinary desktop scheduling | realtime work belongs on deterministic hardware/runtime | +| Hardware timestamping | IEEE 1588/PTP capable timestamp units are used near MAC/PHY boundaries | device timing truth must come from hardware timestamps | +| Controlled abnormal-condition testing | packet impairment, quality changes, sync loss, waveform/fault scenarios and closed-loop tests are reproducible | impairment must be a first-class scenario feature, not an ad-hoc packet hack | +| Evidence and validation | configuration expectations, measured facts and compliance claims remain separate | every claim needs reproducible evidence | + +## 2. Why dedicated hardware changes the product + +A host capture stack is affected by interface buffering, host transport, drivers, capture libraries and OS scheduling. A dedicated instrument can timestamp and classify traffic before those layers distort timing observations. + +ARStack target split: + +```text +Host Dedicated instrument +UI / SCL / reports <----> packet timing / capture / execution +storage / automation process-bus network +``` + +This separation is the foundation for reliable injector and analyzer behavior. + +## 3. Why SCL must drive interoperability + +IEC 61850-6 exists to support interoperable exchange of system/IED configuration. Therefore the canonical ARStack workflow should be: + +```text +SCD/CID/ICD/IID + -> parse + validate + -> select stream/control block + -> bind DataSet and communication address + -> compile immutable runtime profile + -> deploy to realtime hardware +``` + +Hard-coded MAC/APPID/VLAN/dataset values are acceptable only for bring-up or explicitly labeled compatibility tests. + +## 4. What a serious process-bus analyzer must measure + +A useful analyzer should eventually cover: + +1. packet/framing correctness; +2. sequence continuity, duplicates and out-of-order traffic; +3. waveform reconstruction, RMS, frequency, phasor and quality; +4. hardware RX timing and jitter statistics; +5. expected-vs-observed SCL comparison; +6. PTP source/domain/offset/path-delay/lock state; +7. PRP/HSR duplicate-path and asymmetry behavior; +8. triggerable bounded capture and evidence export. + +The engineering surface should speak both raw IEC 61850 fields and electrical quantities. + +## 5. Deterministic injector lessons + +A professional injector architecture should support: + +- deterministic scheduling independent of the desktop host; +- preallocated/fixed hot paths; +- SCL/profile-driven framing and field presence; +- waveform/fault/scenario engines; +- COMTRADE playback; +- controlled packet loss, delay, duplication and reordering; +- quality/test/simulation changes; +- controlled synchronization loss; +- multi-stream scaling with hardware acceleration when MCU margin is no longer sufficient; +- hardware TX timestamp evidence for actual on-wire timing. + +## 6. Standards research that constrains the architecture + +### IEC 61850-9-2 Ed.2.1 + +Defines the specific communication service mapping for Sampled Values over Ethernet/link layer, based on the abstract SV model in IEC 61850-7-2. + +**ARStack rule:** canonical SV framing and ASDU field behavior must be standards/profile driven, not viewer driven. + +Official reference: +- https://webstore.iec.ch/en/publication/66549 + +### IEC 61850-6 Ed.2.2 + +Defines the SCL exchange format intended to support compatible data exchange between system and IED engineering tools. + +**ARStack rule:** SCL is the engineering configuration source; the host compiles it into deterministic runtime profiles. + +Official reference: +- https://webstore.iec.ch/en/publication/103863 + +### IEC TS 61850-6-3:2025 + +Defines a machine-processable rule format for validation of IEC 61850 XML/SCL files. + +**ARStack opportunity:** future SCL validation should evolve toward rule-driven validation rather than accumulating undocumented heuristics. + +Official reference: +- https://webstore.iec.ch/en/publication/79695 + +### IEC/IEEE 61850-9-3:2016 + +Defines the PTP profile for power utility automation. + +**ARStack rule:** seeing PTP traffic is not synchronization evidence. `smpSynch` must derive from measured hardware-clock state. + +Official reference: +- https://webstore.iec.ch/en/publication/24998 + +### IEC 61869-9:2016 + +Defines digital interface requirements for instrument-transformer measurements based on IEC 61850 and network time synchronization. + +**ARStack rule:** IEC 61869-9 is a distinct profile family, not a synonym for generic IEC 61850-9-2 or legacy 9-2LE. + +Official reference: +- https://webstore.iec.ch/en/publication/24663 + +### IEC 61869-13:2021 + +Covers stand-alone merging units and references IEC 61869-9 for digital output format. + +**ARStack opportunity:** future MU/SAMU simulator workflows should be designed against sourced product-family requirements rather than generic packet shapes. + +Official reference: +- https://webstore.iec.ch/en/publication/28359 + +### IEC 61850-10 Ed.2.1 + +Defines conformance-testing techniques for clients, servers, sampled-value devices and engineering tools, including measurement techniques for declared performance parameters. + +**ARStack rule:** evidence bundles and performance claims should be designed toward formal testability. “Works with our analyzer” is not conformance. + +Official reference: +- https://webstore.iec.ch/en/publication/108858 + +### IEC TS 60255-216-1:2025 + +Covers protection functions using digital communication, including SV subscriptions, GOOSE and time synchronization, with functional interoperability/testing concerns. + +**ARStack opportunity:** relay-oriented scenario libraries should ultimately test function behavior, not only network behavior. + +Official reference: +- https://webstore.iec.ch/en/publication/77735 + +### IEC 62439-3 + +Defines PRP and HSR zero-recovery redundancy mechanisms. + +**ARStack rule:** a high-end process-bus analyzer should eventually understand duplicate paths, path asymmetry and redundancy correctness rather than blindly de-duplicate traffic. + +Official reference: +- https://webstore.iec.ch/en/publication/64423 + +### IEC 62351-6 + +Specifies security for protocols based on/derived from IEC 61850, including IEC 61850-9-2 context. + +**ARStack rule:** security belongs in both protocol/tool boundaries and instrument management/update design. + +Official reference: +- https://webstore.iec.ch/en/publication/63742 + +## 7. Current development-platform fit + +The current ESP32-P4 target exposes an IEEE1588v2-capable Ethernet MAC and is suitable for proving: + +- deterministic single-port SV/GOOSE publishing; +- PTP slave/monitor experiments; +- hardware-clock timing evidence; +- mirrored/TAP-output analyzer development; +- host-controlled real-time test endpoints. + +It does **not** make the current one-port board a complete transparent inline precision instrument. The precision hardware roadmap therefore remains multi-port, timestamp-centric and capable of adding a deterministic network/TAP front-end. + +## 8. Product positioning derived from the research + +> **Open real-time IEC 61850 instrumentation — SCL-driven execution, hardware-timestamped measurement, deterministic injection, and reproducible evidence.** + +Where ARStack should aim to be unusually strong: + +- transparent evidence and reproducibility; +- open protocol/runtime layers; +- deterministic RTOS design visible to reviewers; +- clear separation of hardware timing vs host-capture timing; +- SCL expected-vs-observed comparison; +- profile claims backed by explicit evidence; +- low-cost development hardware that can graduate into precision hardware; +- automation-friendly machine-readable reports; +- hardware CI/regression testing; +- a shared core for injector and analyzer. + +## 9. Repository policy + +This repository intentionally avoids commercial brand/product comparison tables, competitor names, screenshots, logos, and vendor marketing links. External research may inform design decisions, but the public repository records only vendor-neutral engineering principles and primary standards references. + +Revisit the standards and industry principles periodically. This document is directional architecture research, not a market-comparison document. \ No newline at end of file diff --git a/embedded/esp32p4_smv_injector/.gitignore b/embedded/esp32p4_smv_injector/.gitignore new file mode 100644 index 000000000..81f73171f --- /dev/null +++ b/embedded/esp32p4_smv_injector/.gitignore @@ -0,0 +1,5 @@ +build/ +sdkconfig +sdkconfig.old +managed_components/ +dependencies.lock diff --git a/embedded/esp32p4_smv_injector/ARCHITECTURE.md b/embedded/esp32p4_smv_injector/ARCHITECTURE.md new file mode 100644 index 000000000..e83b68204 --- /dev/null +++ b/embedded/esp32p4_smv_injector/ARCHITECTURE.md @@ -0,0 +1,186 @@ +# ESP32-P4 SV Injector — standards-first architecture + +This document defines the direction after P0/P1 hardware bring-up and P2 50 Hz realtime publishing proved that the current ESP32-P4 target can sustain a 4000-sample/s SV stream on the bench. + +The design separates: + +- **wire facts** — what is actually observed or timestamped; +- **configured expectations** — what SCL/profile configuration requires; +- **profile claims** — conclusions such as generic IEC 61850-9-2, IEC 61869-9, or legacy 9-2LE compatibility; +- **synchronization evidence** — what is actually known about PTP clock and `smpSynch` state. + +A decoded SV packet is not by itself a conformance claim. + +## 1. Product split + +```text +Host engineering application + | + | SCD/CID/ICD/IID + waveform/fault scenario + v +SCL/profile compiler + |- stream address and identifiers + |- SmvOpts field-presence policy + |- dataset order and element types + |- sample-rate / nofASDU / counter policy + |- scaling and quality policy + |- synchronization policy + v +Versioned compiled SvPublisherProfile + | + | control plane + v +ESP32-P4 realtime runtime + |- validate immutable profile + |- prebuild packet templates + |- waveform/fault engine + |- deterministic SV publisher + |- PTP-disciplined hardware clock + |- truthful smpSynch + |- timing/evidence telemetry +``` + +XML/SCL parsing belongs on the host. It must never occur in the ESP32 realtime TX hot path. + +## 2. Reuse ARStack core instead of creating parallel models + +ARStack already contains: + +- `ariec61850::scl::SclDocument` / `SclSampledValuesStream`; +- dataset entries with CDC/basic-type metadata; +- Ethernet/VLAN types; +- Sampled Values ASDU/PDU/frame codecs; +- sample-counter and stream-supervision utilities. + +The future publisher profile should be compiled from those models rather than duplicating SCL parsing inside the embedded application. + +## 3. Compiled SV profile + +The host should reduce SCL plus an explicitly selected interoperability profile to a compact immutable structure similar to: + +```text +SvPublisherProfile + version + profileFamily + destinationMac + appId + vlanId / vlanPriority + svId + confRev + nofAsdu + fieldPresence + dataSet + refrTm + smpRate + smpMod + dataSetReference + samplingBasis + sampleRate + samplesPerCycle + sampleCounterPolicy + synchronizationPolicy + channels[] + semantic + basicType + byteOffset + scale + qualityPolicy +``` + +Activation must fail closed when required bindings are unresolved or the binary profile fails version/length/checksum validation. + +## 4. Profile claims + +Keep three product-facing families separate: + +1. **Generic IEC 61850-9-2 / SCL driven** — exact configuration follows the selected SCL stream. +2. **IEC 61869-9** — only after required constraints are sourced and validated. +3. **Legacy 9-2LE compatibility** — explicit compatibility mode, never silently inferred as the generic standard. + +Unknown or partially evidenced profile fields stay unknown. The software must not convert a heuristic into a compliance label. + +## 5. Canonical vs diagnostic traffic + +The standards-facing canonical stream and any capture workaround must have separate identities. + +Current bench example: + +- canonical: SCL-derived multicast/VLAN stream; +- diagnostic mirror: untagged broadcast with its own APPID/svID and explicit sampling metadata for raw analyzer reconstruction. + +A diagnostic mirror is never evidence that the multicast/VLAN process-bus stream has passed interoperability testing. + +## 6. Timing hierarchy + +Timing evidence has distinct layers and they must not be mixed: + +```text +sample deadline + -> publisher task wake time + -> EMAC hardware TX timestamp + -> analyzer hardware RX timestamp + -> external reference capture timestamp + -> host capture timestamp +``` + +The current GPTimer/FreeRTOS telemetry proves scheduler continuity, but host USB-Ethernet arrival timestamps can be batched or perturbed and are not the source of truth for device TX jitter. + +P2 should therefore add ESP32-P4 EMAC IEEE1588v2 hardware timestamp evidence before making on-wire timing claims. + +## 7. PTP / IEC 61850-9-3 direction + +Use an evidence-oriented layered design and move standards-facing synchronization onto hardware time: + +1. PTPv2 Layer-2 codec (`0x88F7`). +2. Passive monitor: observed sources, domain, message types, sequence integrity. +3. IEC/IEEE 61850-9-3 Power Profile validator. +4. ESP32-P4 EMAC IEEE1588v2 hardware clock. +5. Peer-delay / offset estimation and clock servo. +6. Lock / holdover / unlocked state machine. +7. SV `smpSynch` policy derived from measured state. +8. Hardware TX timestamp evidence correlated with `smpCnt`. + +Software-generated lab PTP traffic must never be treated as proof of hardware synchronization. Traffic visibility, clock discipline and synchronization claims remain separate. + +## 8. `smpSynch` rule + +Standards mode: + +```text +no valid synchronization evidence -> unsynchronized +measured local synchronization -> local according to selected profile +measured global/PTP synchronization-> global according to selected profile +``` + +Compatibility overrides, if later offered for relay readability tests, must be visibly labelled as compatibility behavior and never treated as timing evidence. + +## 9. Current bench interpretation + +The current 50 Hz build has demonstrated: + +- approximately 4000 SV samples/s; +- continuous sequence on the diagnostic stream; +- balanced reconstructed 50 Hz three-phase waveforms; +- zero device-side TX failures and missed GPTimer notifications in observed stable runs; +- a host USB-Ethernet capture path that reports about the correct average throughput but large individual arrival-time excursions. + +Therefore the next timing work is **hardware timestamp evidence**, not compensating the publisher against host capture jitter. + +## 10. Acceptance gates + +A production-grade result requires independent gates for: + +- packet/framing correctness; +- SCL/configuration match; +- dataset semantics and engineering scaling; +- canonical multicast/VLAN interoperability; +- sustained 4000/4800 sample/s integrity; +- hardware TX timing statistics; +- PTP synchronization quality and truthful `smpSynch`; +- relay/subscriber interoperability; +- evidence artifacts (PCAP + machine-readable timing/profile report). + +Tracked work: + +- P2 deterministic timing evidence: issue #21 +- standards-first SCL/profile/PTP architecture: issue #24 \ No newline at end of file diff --git a/embedded/esp32p4_smv_injector/CMakeLists.txt b/embedded/esp32p4_smv_injector/CMakeLists.txt new file mode 100644 index 000000000..de0fe8a80 --- /dev/null +++ b/embedded/esp32p4_smv_injector/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(arstack_esp32p4_smv_injector) diff --git a/embedded/esp32p4_smv_injector/README.md b/embedded/esp32p4_smv_injector/README.md new file mode 100644 index 000000000..7979e12c3 --- /dev/null +++ b/embedded/esp32p4_smv_injector/README.md @@ -0,0 +1,163 @@ +# ESP32-P4-ETH SMV Injector — P0/P1 + P2 50 Hz bring-up + +This target brings up the current ESP32-P4 Ethernet development board using the internal EMAC + onboard RMII PHY and publishes IEC 61850 Sampled Values through the existing ARStack process-bus codec. + +See [`ARCHITECTURE.md`](ARCHITECTURE.md) for the standards-first SCL/profile/PTP direction after the current P2 bring-up. + +## Current hardware result + +P0/P1 and the first P2 realtime path have been proven on real hardware: + +- ESP32-P4 rev 1.3 boots and drives the onboard RMII PHY. +- Raw Layer-2 transmit reaches a host capture NIC. +- Independent packet capture recognizes the generated `0x88BA` payload as IEC 61850 Sampled Values. +- The GPTimer-driven publisher sustains about 4000 samples/s on the current bench with device-side TX failures and missed timer notifications at zero in the observed run. +- A raw process-bus analyzer reconstructs balanced 50 Hz three-phase voltage/current waveforms from the diagnostic stream and reports continuous sequence integrity. +- The tested USB-Ethernet host capture path reports roughly the correct average rate but shows large individual arrival-time excursions. Treat those timestamps as capture-path evidence, not device-TX timing evidence. +- The tested host capture path does not reliably expose the normal IEC 61850 multicast/priority-tagged stream. The firmware therefore keeps a lab-only untagged broadcast diagnostic stream so waveform/timing validation can continue without pretending that stream is the standards-facing output. + +## P2 50 Hz publisher + +The default firmware enables the first realtime publisher foundation: + +- 50 Hz nominal frequency. +- 80 samples/cycle. +- 4000 frames/s. +- 250 us GPTimer period. +- canonical `smpCnt` wraps at 4000. +- prebuilt fixed-size frame templates; no BER encode or heap allocation in the hot TX loop. +- fixed 2-byte `smpCnt` and 4-byte `confRev` patch fields in the runtime template. +- balanced three-phase sine generator. +- dataset order `Ia, Ib, Ic, In, Ua, Ub, Uc, Un`. +- each channel is `(INT32 sample, UINT32 quality)`. +- current payload scaling: 1 mA/count. +- voltage payload scaling: 10 mV/count. +- default phase current: 1.000 A RMS. +- default phase-to-neutral voltage: approximately 57.74 V RMS (100 V line-to-line). +- `smpSynch=0` remains truthful until a measured PTP/1PPS lock exists. + +### Standards-facing canonical stream + +The current canonical stream is aligned with `01_SV_Stream_4I+4V_(9-2LE).scd` stream #1: + +- destination `01:0C:CD:04:00:01`; +- APPID `0x4000`; +- `svID=MU01_SV1`; +- `confRev=1`; +- VLAN PCP 4 / VID 0 enabled by default; +- one ASDU; +- dataset-reference field omitted because the supplied SCL has `dataSet=false`; +- explicit `smpRate` / `smpMod` fields omitted because the supplied SCL has `sampleRate=false`. + +This is the interoperability target. The build still does **not** claim final strict 9-2LE/IEC 61869-9 conformance; exact profile rules, synchronization, canonical multicast/VLAN capture on a trusted path and relay interoperability remain acceptance gates. + +### Lab diagnostic stream + +For the current host-capture limitation the firmware also publishes a separate diagnostic stream: + +- destination `FF:FF:FF:FF:FF:FF`; +- APPID `0x4F01`; +- `svID=AR_DIAG_SV1`; +- untagged; +- explicit `smpRate=4000` and `smpMod=SmpPerSec` so a raw analyzer can reconstruct the waveform without SCL binding; +- independent normal 16-bit sample counter. + +The diagnostic identity is intentionally different from the canonical stream so analyzers do not merge both sequences. This stream is a capture/debug aid, not a process-bus compliance target. + +## Board wiring used + +| Signal | GPIO | +|---|---:| +| PHY RESET | 51 | +| MDC | 31 | +| MDIO | 52 | +| RMII REF_CLK input | 50 | +| TX_EN | 49 | +| TXD0 | 34 | +| TXD1 | 35 | +| CRS_DV | 28 | +| RXD0 | 29 | +| RXD1 | 30 | +| PHY address | 1 | + +## Toolchain + +Use ESP-IDF 5.5.x. If the board contains an early ESP32-P4 engineering sample, select the matching chip revision configuration before flashing. For a detected ESP32-P4 v1.x/v2.x board, enable the ESP-IDF pre-v3 chip-revision option and rebuild. Do not bypass a revision mismatch with `--force`. + +The currently tested board is ESP32-P4 rev 1.3, so its local `sdkconfig` must retain the pre-v3 revision setting. + +## Build and flash + +```bash +cd embedded/esp32p4_smv_injector +idf.py build +idf.py -p flash monitor +``` + +For the tested bench the port is currently `COM3`. + +After boot, expected serial output includes: + +```text +P2 waveform: 50 Hz, 80 samples/cycle, 4000 fps, period=250 us +SCL canonical: svID=MU01_SV1 ... APPID=0x4000 ... +LAB mirror: svID=AR_DIAG_SV1 APPID=0x4F01 ... explicit smpRate=4000 smpMod=SmpPerSec +P2 timing: samples=... MC ok=... fail=... mirror ok=... fail=... missed=... wake_late_us[min/mean/max]=... +``` + +## Capture / analyzer checks + +For the current host capture adapter, use the diagnostic stream first: + +```text +eth.type == 0x88ba +``` + +Expected diagnostic behavior: + +- roughly 4000 decoded SV frames/s; +- continuous 16-bit `smpCnt` progression; +- visible non-zero three-phase current and voltage samples; +- reconstructed 80 samples/cycle at 50 Hz; +- balanced A/B/C waveform and phasors separated by 120 degrees. + +The standards-facing multicast stream is sent in parallel. A trusted capture path should validate: + +```text +eth.dst == 01:0c:cd:04:00:01 +vlan.etype == 0x88ba +``` + +Do not use host USB-Ethernet arrival jitter as the final device timing metric. The next P2 timing gate is ESP32-P4 EMAC IEEE1588v2 hardware TX timestamps and/or an independent reference capture path. + +## Configuration + +Run `idf.py menuconfig` and open **ARStack ESP32-P4 SMV Injector** to control: + +- live TX; +- P2 50 Hz realtime mode; +- canonical VLAN PCP4/VID0; +- lab-only untagged diagnostic stream; +- optional `0x88B5` capture probes; +- current RMS in mA; +- voltage RMS in mV. + +Use an isolated bench network or a direct capture/TAP connection. Do not connect this development image to an operational substation process bus. + +## Remaining P2 acceptance work + +Before calling the publisher production-grade: + +- validate 60 seconds of 4000 fps capture with zero unexplained `smpCnt` gaps; +- enable ESP32-P4 EMAC IEEE1588v2 hardware time and collect hardware TX timestamp evidence; +- calculate scheduled-vs-actual TX min/max/mean/P95/P99 timing error; +- validate canonical VLAN multicast on a trusted capture path; +- add 60 Hz / 4800 fps scheduling; +- add IEC/IEEE 61850-9-3 PTP synchronization before changing `smpSynch` from 0; +- move from hard-coded bench constants to an SCL/profile-compiled runtime configuration; +- validate exact IEC 61850-9-2 / IEC 61869-9 / legacy 9-2LE behavior against sourced profile rules and independent relay/tool testing. + +Tracked work: + +- P2 timing evidence: issue #21 +- standards-first SCL/profile/PTP engine: issue #24 \ No newline at end of file diff --git a/embedded/esp32p4_smv_injector/components/arstack_process_bus/CMakeLists.txt b/embedded/esp32p4_smv_injector/components/arstack_process_bus/CMakeLists.txt new file mode 100644 index 000000000..a5c7a57d5 --- /dev/null +++ b/embedded/esp32p4_smv_injector/components/arstack_process_bus/CMakeLists.txt @@ -0,0 +1,13 @@ +set(ARSTACK_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../../..") + +idf_component_register( + SRCS + "${ARSTACK_ROOT}/src/asn1/ber.cpp" + "${ARSTACK_ROOT}/src/ethernet/ethernet.cpp" + "${ARSTACK_ROOT}/src/mms/utc_time.cpp" + "${ARSTACK_ROOT}/src/sampled_values/pdu_codec.cpp" + "${ARSTACK_ROOT}/src/sampled_values/frame_codec.cpp" + INCLUDE_DIRS + "${ARSTACK_ROOT}/include") + +target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_20) diff --git a/embedded/esp32p4_smv_injector/main/CMakeLists.txt b/embedded/esp32p4_smv_injector/main/CMakeLists.txt new file mode 100644 index 000000000..1bd39c6ee --- /dev/null +++ b/embedded/esp32p4_smv_injector/main/CMakeLists.txt @@ -0,0 +1,8 @@ +idf_component_register( + SRCS + "app_main.cpp" + "ethernet_port.c" + INCLUDE_DIRS "." + REQUIRES arstack_process_bus esp_eth esp_event esp_timer esp_driver_gptimer freertos) + +target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_20) diff --git a/embedded/esp32p4_smv_injector/main/Kconfig.projbuild b/embedded/esp32p4_smv_injector/main/Kconfig.projbuild new file mode 100644 index 000000000..69aee4aba --- /dev/null +++ b/embedded/esp32p4_smv_injector/main/Kconfig.projbuild @@ -0,0 +1,68 @@ +menu "ARStack ESP32-P4 SMV Injector" + +config AR_SMV_LIVE_TX + bool "Enable raw Layer-2 Sampled Values transmission" + default n + help + Enables active IEC 61850 Sampled Values Ethernet transmission after + the PHY reports link-up. Use only on an isolated laboratory process-bus + network or a direct capture-NIC connection. + +config AR_SMV_P2_REALTIME_50HZ + bool "Enable P2 50 Hz / 80 samples-per-cycle realtime publisher" + default y + depends on AR_SMV_LIVE_TX + help + Publishes 4000 Sampled Values frames per second with a 250 us hardware + timer schedule, 4000-count canonical smpCnt wrap, prebuilt frame + templates and a generated balanced three-phase waveform. + +config AR_SMV_CANONICAL_VLAN + bool "Tag canonical multicast SV with VLAN PCP 4 / VID 0" + default y + depends on AR_SMV_P2_REALTIME_50HZ + help + Keeps the standards-facing multicast stream priority tagged. Disable + only for targeted capture-path diagnosis. + +config AR_SMV_BROADCAST_MIRROR + bool "Enable isolated untagged broadcast capture mirror" + default y + depends on AR_SMV_P2_REALTIME_50HZ + help + Sends a lab-only untagged broadcast diagnostic stream for capture + adapters/drivers that discard IEC 61850 multicast or priority-tagged + frames. The mirror uses its own APPID/svID and a normal 16-bit smpCnt + sequence so analysis software cannot merge it with the canonical SCL + stream. It also carries smpRate/smpMod explicitly for raw waveform + reconstruction. Do not treat this mirror as the standards-facing + process-bus stream. + +config AR_SMV_DIAGNOSTIC_PROBES + bool "Enable 0x88B5 broadcast/multicast probes once per second" + default n + depends on AR_SMV_P2_REALTIME_50HZ + help + Adds low-rate non-SV probes for capture-path troubleshooting. Keep this + disabled while measuring realtime timing unless it is needed. + +config AR_SMV_CURRENT_RMS_MA + int "Balanced phase current RMS (mA)" + range 0 100000 + default 1000 + depends on AR_SMV_P2_REALTIME_50HZ + help + RMS current used by the built-in waveform generator. The 9-2LE-shaped + payload uses 1 mA per integer count. + +config AR_SMV_VOLTAGE_RMS_MV + int "Balanced phase-to-neutral voltage RMS (mV)" + range 0 100000000 + default 57740 + depends on AR_SMV_P2_REALTIME_50HZ + help + RMS voltage used by the built-in waveform generator. The 9-2LE-shaped + payload uses 10 mV per integer count. The default is approximately + 57.74 V phase-to-neutral (100 V line-to-line). + +endmenu diff --git a/embedded/esp32p4_smv_injector/main/app_main.cpp b/embedded/esp32p4_smv_injector/main/app_main.cpp new file mode 100644 index 000000000..a65bfa7b6 --- /dev/null +++ b/embedded/esp32p4_smv_injector/main/app_main.cpp @@ -0,0 +1,641 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ariec61850/ethernet/ethernet.hpp" +#include "ariec61850/sampled_values/asdu.hpp" +#include "ariec61850/sampled_values/frame.hpp" +#include "ariec61850/sampled_values/frame_codec.hpp" + +#include "driver/gptimer.h" +#include "ethernet_port.h" +#include "esp_attr.h" +#include "esp_err.h" +#include "esp_eth.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "freertos/task.h" +#include "sdkconfig.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using ar::iec61850::ethernet::MacAddress; +using ar::iec61850::ethernet::VlanTag; +using ar::iec61850::sampled_values::SampledValueAsdu; +using ar::iec61850::sampled_values::SampledValuesFrame; +using ar::iec61850::sampled_values::SampledValuesFrameCodec; +using ar::iec61850::sampled_values::SampledValuesPdu; + +constexpr char kTag[] = "ar_smv_p2"; +constexpr EventBits_t kLinkUpBit = BIT0; + +// Canonical stream identity is intentionally aligned with +// samples/scl/01_SV_Stream_4I+4V_(9-2LE).scd stream #1. +constexpr std::uint16_t kCanonicalAppId = 0x4000U; +constexpr char kCanonicalSvId[] = "MU01_SV1"; +constexpr std::array kSvMulticastMac{ + 0x01U, 0x0CU, 0xCDU, 0x04U, 0x00U, 0x01U}; + +// The broadcast path exists only because the currently tested host capture +// path does not reliably expose the standards-facing multicast/VLAN stream. +// Give it a separate identity so analyzers never merge canonical and mirror +// samples into one sequence. +constexpr std::uint16_t kMirrorAppId = 0x4F01U; +constexpr char kMirrorSvId[] = "AR_DIAG_SV1"; + +constexpr std::uint16_t kSampleCountWrap = 4000U; +constexpr std::uint32_t kSampleRateHz = 4000U; +constexpr std::uint32_t kSamplesPerCycle = 80U; +constexpr std::int64_t kSamplePeriodUs = 250; +constexpr std::uint16_t kSampleModeSamplesPerSecond = 1U; +constexpr std::uint16_t kDiagnosticEtherType = 0x88B5U; +constexpr std::uint32_t kStatsMergeEvery = 400U; +constexpr double kPi = 3.14159265358979323846; + +EventGroupHandle_t g_link_events = nullptr; +portMUX_TYPE g_stats_mux = portMUX_INITIALIZER_UNLOCKED; + +struct TimingStats final { + std::uint64_t samples{}; + std::uint64_t canonical_ok{}; + std::uint64_t canonical_fail{}; + std::uint64_t mirror_ok{}; + std::uint64_t mirror_fail{}; + std::uint64_t missed_slots{}; + std::int64_t lateness_sum_us{}; + std::int32_t lateness_min_us{std::numeric_limits::max()}; + std::int32_t lateness_max_us{std::numeric_limits::min()}; + std::uint16_t last_sample_count{}; +}; + +TimingStats g_interval_stats{}; + +struct PacketTemplate final { + std::vector bytes; + std::size_t sample_count_offset{std::numeric_limits::max()}; + std::size_t sample_payload_offset{std::numeric_limits::max()}; +}; + +using WaveformRow = std::array; +using WaveformTable = std::array; + +void eth_event_handler(void*, esp_event_base_t, const std::int32_t event_id, void*) { + switch (event_id) { + case ETHERNET_EVENT_CONNECTED: + xEventGroupSetBits(g_link_events, kLinkUpBit); + ESP_LOGI(kTag, "Ethernet link up"); + break; + case ETHERNET_EVENT_DISCONNECTED: + xEventGroupClearBits(g_link_events, kLinkUpBit); + ESP_LOGW(kTag, "Ethernet link down"); + break; + case ETHERNET_EVENT_START: + ESP_LOGI(kTag, "Ethernet driver started"); + break; + case ETHERNET_EVENT_STOP: + ESP_LOGI(kTag, "Ethernet driver stopped"); + break; + default: + break; + } +} + +void write_u16_be(std::uint8_t* destination, const std::uint16_t value) noexcept { + destination[0] = static_cast((value >> 8U) & 0xFFU); + destination[1] = static_cast(value & 0xFFU); +} + +void write_u32_be(std::uint8_t* destination, const std::uint32_t value) noexcept { + destination[0] = static_cast((value >> 24U) & 0xFFU); + destination[1] = static_cast((value >> 16U) & 0xFFU); + destination[2] = static_cast((value >> 8U) & 0xFFU); + destination[3] = static_cast(value & 0xFFU); +} + +void write_i32_be(std::uint8_t* destination, const std::int32_t value) noexcept { + write_u32_be(destination, static_cast(value)); +} + +std::vector make_9_2le_shape_payload() { + // Dataset order from the SCL: Ia, Ib, Ic, In, Ua, Ub, Uc, Un. + // Each value is INT32 followed by UINT32 Quality. + return std::vector(8U * 8U, 0U); +} + +SampledValuesFrame make_runtime_frame( + const std::array& source_mac, + const bool use_vlan, + const bool diagnostic_mirror) { + SampledValueAsdu asdu; + asdu.sv_id = diagnostic_mirror ? kMirrorSvId : kCanonicalSvId; + + // The supplied SCL has SmvOpts dataSet=false, so the canonical ASDU does + // not carry the dataset-reference field. + asdu.data_set_reference.clear(); + + // Fixed-width placeholders. The generic BER writer is minimal-width, while + // the realtime hot path must patch smpCnt/confRev in place without changing + // packet length. + asdu.sample_count = 0x0100U; + asdu.configuration_revision = 0x01000000U; + asdu.reference_time.reset(); + asdu.sample_synchronization = 0U; // truthful: no measured PTP/1PPS lock. + + if (diagnostic_mirror) { + // Raw process-bus reconstruction needs these fields when SCL is not + // bound. Keep them on the diagnostic stream only; canonical output + // remains aligned with the supplied SCL SmvOpts sampleRate=false. + asdu.sample_rate = static_cast(kSampleRateHz); + asdu.sample_mode = kSampleModeSamplesPerSecond; + } else { + asdu.sample_rate.reset(); + asdu.sample_mode.reset(); + } + + asdu.sample_payload = make_9_2le_shape_payload(); + + SampledValuesFrame frame; + frame.destination = MacAddress{std::span{kSvMulticastMac}}; + frame.source = MacAddress{std::span{source_mac}}; + if (use_vlan) { + frame.vlan = VlanTag{4U, 0U}; + } else { + frame.vlan.reset(); + } + frame.app_id = diagnostic_mirror ? kMirrorAppId : kCanonicalAppId; + frame.reserved1 = 0U; + frame.reserved2 = 0U; + frame.pdu = SampledValuesPdu{{std::move(asdu)}}; + return frame; +} + +bool find_fixed_field( + const std::vector& bytes, + const std::uint8_t tag, + const std::uint8_t length, + std::size_t& value_offset) noexcept { + for (std::size_t index = 20U; index + 2U + length <= bytes.size(); ++index) { + if (bytes[index] == tag && bytes[index + 1U] == length) { + value_offset = index + 2U; + return true; + } + } + return false; +} + +bool build_packet_template( + const std::array& source_mac, + const bool use_vlan, + const bool broadcast_destination, + const bool diagnostic_mirror, + PacketTemplate& packet) { + packet = {}; + packet.bytes = SampledValuesFrameCodec::encode( + make_runtime_frame(source_mac, use_vlan, diagnostic_mirror)); + + if (broadcast_destination) { + std::fill_n(packet.bytes.begin(), 6U, std::uint8_t{0xFFU}); + } + + std::size_t conf_rev_offset = 0U; + if (!find_fixed_field(packet.bytes, 0x82U, 2U, packet.sample_count_offset) || + !find_fixed_field(packet.bytes, 0x83U, 4U, conf_rev_offset) || + !find_fixed_field(packet.bytes, 0x87U, 64U, packet.sample_payload_offset)) { + ESP_LOGE(kTag, "Failed to locate fixed SV patch fields in encoded template"); + return false; + } + + if (packet.sample_payload_offset + 64U > packet.bytes.size()) { + ESP_LOGE(kTag, "SV payload patch region exceeds encoded frame"); + return false; + } + + write_u16_be(packet.bytes.data() + packet.sample_count_offset, 0U); + write_u32_be(packet.bytes.data() + conf_rev_offset, 1U); + return true; +} + +WaveformTable make_waveform_table() { + WaveformTable table{}; + + // 9-2LE-shaped integer scaling retained on the publisher side: + // current 1 mA/count, voltage 10 mV/count. + const double current_peak_counts = + static_cast(CONFIG_AR_SMV_CURRENT_RMS_MA) * std::sqrt(2.0); + const double voltage_peak_counts = + (static_cast(CONFIG_AR_SMV_VOLTAGE_RMS_MV) * std::sqrt(2.0)) / 10.0; + + for (std::size_t sample = 0U; sample < table.size(); ++sample) { + const double angle_a = (2.0 * kPi * static_cast(sample)) / + static_cast(kSamplesPerCycle); + const double angle_b = angle_a - (2.0 * kPi / 3.0); + const double angle_c = angle_a + (2.0 * kPi / 3.0); + + const auto ia = static_cast( + std::lround(current_peak_counts * std::sin(angle_a))); + const auto ib = static_cast( + std::lround(current_peak_counts * std::sin(angle_b))); + const auto ic = static_cast( + std::lround(current_peak_counts * std::sin(angle_c))); + const auto in = static_cast(ia + ib + ic); + + const auto ua = static_cast( + std::lround(voltage_peak_counts * std::sin(angle_a))); + const auto ub = static_cast( + std::lround(voltage_peak_counts * std::sin(angle_b))); + const auto uc = static_cast( + std::lround(voltage_peak_counts * std::sin(angle_c))); + const auto un = static_cast(ua + ub + uc); + + table[sample] = {ia, ib, ic, in, ua, ub, uc, un}; + } + + return table; +} + +void patch_packet( + PacketTemplate& packet, + const std::uint16_t sample_count, + const WaveformRow& values) noexcept { + write_u16_be(packet.bytes.data() + packet.sample_count_offset, sample_count); + + for (std::size_t channel = 0U; channel < values.size(); ++channel) { + write_i32_be(packet.bytes.data() + packet.sample_payload_offset + channel * 8U, + values[channel]); + } +} + +#if CONFIG_AR_SMV_DIAGNOSTIC_PROBES +std::array make_probe( + const std::array& source_mac, + const bool multicast) { + std::array frame{}; + + for (std::size_t index = 0U; index < 6U; ++index) { + frame[index] = multicast ? kSvMulticastMac[index] : 0xFFU; + frame[6U + index] = source_mac[index]; + } + + frame[12] = static_cast((kDiagnosticEtherType >> 8U) & 0xFFU); + frame[13] = static_cast(kDiagnosticEtherType & 0xFFU); + + constexpr char payload[] = "ARSTACK-P2-CAPTURE-PROBE"; + for (std::size_t index = 0U; + index < sizeof(payload) - 1U && 14U + index < frame.size(); + ++index) { + frame[14U + index] = static_cast(payload[index]); + } + + return frame; +} +#endif + +void merge_stats(TimingStats& local) noexcept { + if (local.samples == 0U) { + return; + } + + portENTER_CRITICAL(&g_stats_mux); + g_interval_stats.samples += local.samples; + g_interval_stats.canonical_ok += local.canonical_ok; + g_interval_stats.canonical_fail += local.canonical_fail; + g_interval_stats.mirror_ok += local.mirror_ok; + g_interval_stats.mirror_fail += local.mirror_fail; + g_interval_stats.missed_slots += local.missed_slots; + g_interval_stats.lateness_sum_us += local.lateness_sum_us; + g_interval_stats.lateness_min_us = + std::min(g_interval_stats.lateness_min_us, local.lateness_min_us); + g_interval_stats.lateness_max_us = + std::max(g_interval_stats.lateness_max_us, local.lateness_max_us); + g_interval_stats.last_sample_count = local.last_sample_count; + portEXIT_CRITICAL(&g_stats_mux); + + local = TimingStats{}; +} + +void update_local_timing( + TimingStats& local, + const std::int64_t lateness_us, + const std::uint64_t missed_slots, + const std::uint16_t sample_count) noexcept { + const auto bounded_lateness = static_cast(std::clamp( + lateness_us, + std::numeric_limits::min(), + std::numeric_limits::max())); + + ++local.samples; + local.missed_slots += missed_slots; + local.lateness_sum_us += lateness_us; + local.lateness_min_us = std::min(local.lateness_min_us, bounded_lateness); + local.lateness_max_us = std::max(local.lateness_max_us, bounded_lateness); + local.last_sample_count = sample_count; +} + +bool IRAM_ATTR sample_alarm_callback( + gptimer_handle_t, + const gptimer_alarm_event_data_t*, + void* user_context) { + auto task = static_cast(user_context); + BaseType_t high_priority_task_woken = pdFALSE; + vTaskNotifyGiveFromISR(task, &high_priority_task_woken); + return high_priority_task_woken == pdTRUE; +} + +gptimer_handle_t start_sample_clock(TaskHandle_t publisher_task_handle) { + gptimer_config_t timer_config{}; + timer_config.clk_src = GPTIMER_CLK_SRC_DEFAULT; + timer_config.direction = GPTIMER_COUNT_UP; + timer_config.resolution_hz = 1000000U; + + gptimer_handle_t timer = nullptr; + ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &timer)); + + gptimer_event_callbacks_t callbacks{}; + callbacks.on_alarm = &sample_alarm_callback; + ESP_ERROR_CHECK(gptimer_register_event_callbacks( + timer, &callbacks, publisher_task_handle)); + + gptimer_alarm_config_t alarm_config{}; + alarm_config.alarm_count = static_cast(kSamplePeriodUs); + alarm_config.reload_count = 0U; + alarm_config.flags.auto_reload_on_alarm = true; + ESP_ERROR_CHECK(gptimer_set_alarm_action(timer, &alarm_config)); + ESP_ERROR_CHECK(gptimer_enable(timer)); + ESP_ERROR_CHECK(gptimer_start(timer)); + return timer; +} + +void publisher_task(void* argument) { +#if CONFIG_AR_SMV_LIVE_TX && CONFIG_AR_SMV_P2_REALTIME_50HZ + const auto eth_handle = static_cast(argument); + + ESP_LOGW(kTag, "LIVE P2 TX ENABLED: isolated lab network only"); + ESP_LOGI(kTag, "Waiting for Ethernet link..."); + xEventGroupWaitBits(g_link_events, kLinkUpBit, pdFALSE, pdTRUE, portMAX_DELAY); + + std::array source_mac{}; + ESP_ERROR_CHECK(esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, source_mac.data())); + + ESP_LOGI(kTag, + "EMAC source MAC=%02X:%02X:%02X:%02X:%02X:%02X", + source_mac[0], source_mac[1], source_mac[2], + source_mac[3], source_mac[4], source_mac[5]); + + PacketTemplate canonical; + PacketTemplate mirror; + + if (!build_packet_template( + source_mac, +#if CONFIG_AR_SMV_CANONICAL_VLAN + true, +#else + false, +#endif + false, + false, + canonical)) { + ESP_LOGE(kTag, "Cannot start P2 publisher: canonical template build failed"); + vTaskDelete(nullptr); + return; + } + +#if CONFIG_AR_SMV_BROADCAST_MIRROR + if (!build_packet_template(source_mac, false, true, true, mirror)) { + ESP_LOGE(kTag, "Cannot start P2 publisher: broadcast mirror template build failed"); + vTaskDelete(nullptr); + return; + } +#endif + + const auto waveform = make_waveform_table(); + +#if CONFIG_AR_SMV_DIAGNOSTIC_PROBES + const auto probe_broadcast = make_probe(source_mac, false); + const auto probe_multicast = make_probe(source_mac, true); +#endif + + ESP_LOGI(kTag, + "P2 waveform: 50 Hz, 80 samples/cycle, %u fps, period=%lld us, canonical smpCnt wrap=%u", + static_cast(kSampleRateHz), + static_cast(kSamplePeriodUs), + static_cast(kSampleCountWrap)); + ESP_LOGI(kTag, + "Waveform RMS: I=%d mA/phase, U=%d mV phase-neutral; dataset order Ia Ib Ic In Ua Ub Uc Un", + CONFIG_AR_SMV_CURRENT_RMS_MA, + CONFIG_AR_SMV_VOLTAGE_RMS_MV); + ESP_LOGI(kTag, + "SCL canonical: svID=%s dst=01:0C:CD:04:00:01 APPID=0x%04X %s confRev=1 smpSynch=0", + kCanonicalSvId, + static_cast(kCanonicalAppId), +#if CONFIG_AR_SMV_CANONICAL_VLAN + "VLAN PCP4/VID0" +#else + "untagged" +#endif + ); +#if CONFIG_AR_SMV_BROADCAST_MIRROR + ESP_LOGW(kTag, + "LAB mirror: svID=%s APPID=0x%04X untagged broadcast; explicit smpRate=4000 smpMod=SmpPerSec; independent 16-bit smpCnt", + kMirrorSvId, + static_cast(kMirrorAppId)); +#endif + ESP_LOGI(kTag, + "Fixed templates: canonical len=%u smpCnt@%u seqData@%u", + static_cast(canonical.bytes.size()), + static_cast(canonical.sample_count_offset), + static_cast(canonical.sample_payload_offset)); + + vTaskDelay(pdMS_TO_TICKS(100)); + const gptimer_handle_t sample_clock = start_sample_clock(xTaskGetCurrentTaskHandle()); + (void)sample_clock; + ESP_LOGI(kTag, + "P2 sample clock: GPTimer 1 MHz, periodic alarm every %lld us; publisher blocks between alarms", + static_cast(kSamplePeriodUs)); + + std::uint16_t canonical_sample_count = 0U; + std::uint16_t mirror_sample_count = 0U; + TimingStats local{}; + bool schedule_anchored = false; + std::int64_t expected_wake_us = 0; + + while (true) { + const std::uint32_t pending_ticks = ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + if (pending_ticks == 0U) { + continue; + } + + const std::int64_t actual_wake_us = esp_timer_get_time(); + std::uint64_t missed_slots = 0U; + + if (!schedule_anchored) { + expected_wake_us = actual_wake_us; + schedule_anchored = true; + } else { + expected_wake_us += + static_cast(pending_ticks) * kSamplePeriodUs; + } + + if (pending_ticks > 1U) { + missed_slots = static_cast(pending_ticks - 1U); + canonical_sample_count = static_cast( + (static_cast(canonical_sample_count) + missed_slots) % + kSampleCountWrap); + mirror_sample_count = static_cast( + static_cast(mirror_sample_count) + + static_cast(missed_slots)); + } + + const std::int64_t lateness_us = actual_wake_us - expected_wake_us; + + if ((xEventGroupGetBits(g_link_events) & kLinkUpBit) == 0U) { + ++local.canonical_fail; +#if CONFIG_AR_SMV_BROADCAST_MIRROR + ++local.mirror_fail; +#endif + } else { + const auto& row = waveform[canonical_sample_count % kSamplesPerCycle]; + patch_packet(canonical, canonical_sample_count, row); + + const esp_err_t canonical_result = + esp_eth_transmit(eth_handle, canonical.bytes.data(), canonical.bytes.size()); + if (canonical_result == ESP_OK) { + ++local.canonical_ok; + } else { + ++local.canonical_fail; + } + +#if CONFIG_AR_SMV_BROADCAST_MIRROR + patch_packet(mirror, mirror_sample_count, row); + const esp_err_t mirror_result = + esp_eth_transmit(eth_handle, mirror.bytes.data(), mirror.bytes.size()); + if (mirror_result == ESP_OK) { + ++local.mirror_ok; + } else { + ++local.mirror_fail; + } +#endif + +#if CONFIG_AR_SMV_DIAGNOSTIC_PROBES + if ((canonical_sample_count % kSampleCountWrap) == 0U) { + (void)esp_eth_transmit( + eth_handle, + const_cast(probe_broadcast.data()), + probe_broadcast.size()); + (void)esp_eth_transmit( + eth_handle, + const_cast(probe_multicast.data()), + probe_multicast.size()); + } +#endif + } + + update_local_timing(local, lateness_us, missed_slots, canonical_sample_count); + + canonical_sample_count = static_cast( + (canonical_sample_count + 1U) % kSampleCountWrap); + mirror_sample_count = static_cast(mirror_sample_count + 1U); + + if (local.samples >= kStatsMergeEvery) { + merge_stats(local); + } + } +#else + (void)argument; + ESP_LOGW(kTag, "P2 realtime publisher is disabled in menuconfig"); + vTaskDelete(nullptr); +#endif +} + +void telemetry_task(void*) { + while (true) { + vTaskDelay(pdMS_TO_TICKS(1000)); + + TimingStats snapshot{}; + portENTER_CRITICAL(&g_stats_mux); + snapshot = g_interval_stats; + g_interval_stats = TimingStats{}; + portEXIT_CRITICAL(&g_stats_mux); + + if (snapshot.samples == 0U) { + continue; + } + + const std::int64_t mean_lateness_us = + snapshot.lateness_sum_us / static_cast(snapshot.samples); + + ESP_LOGI(kTag, + "P2 timing: samples=%llu (~%llu fps) MC ok=%llu fail=%llu mirror ok=%llu fail=%llu missed=%llu wake_late_us[min/mean/max]=%ld/%lld/%ld canonical_smpCnt=%u", + static_cast(snapshot.samples), + static_cast(snapshot.samples), + static_cast(snapshot.canonical_ok), + static_cast(snapshot.canonical_fail), + static_cast(snapshot.mirror_ok), + static_cast(snapshot.mirror_fail), + static_cast(snapshot.missed_slots), + static_cast(snapshot.lateness_min_us), + static_cast(mean_lateness_us), + static_cast(snapshot.lateness_max_us), + static_cast(snapshot.last_sample_count)); + } +} + +} // namespace + +extern "C" void app_main(void) { + g_link_events = xEventGroupCreate(); + if (g_link_events == nullptr) { + ESP_LOGE(kTag, "Failed to allocate Ethernet event group"); + return; + } + + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_ERROR_CHECK(esp_event_handler_register( + ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, nullptr)); + + esp_eth_handle_t eth_handle = ar_esp32p4_eth_init(); + if (eth_handle == nullptr) { + ESP_LOGE(kTag, "ESP32-P4-ETH bring-up failed"); + return; + } + + ESP_ERROR_CHECK(esp_eth_start(eth_handle)); + +#if CONFIG_AR_SMV_LIVE_TX && CONFIG_AR_SMV_P2_REALTIME_50HZ + if (xTaskCreatePinnedToCore( + &publisher_task, + "ar_smv_publisher", + 8192, + eth_handle, + configMAX_PRIORITIES - 2, + nullptr, + 1) != pdPASS) { + ESP_LOGE(kTag, "Failed to create realtime publisher task on CPU1"); + return; + } + + if (xTaskCreatePinnedToCore( + &telemetry_task, + "ar_smv_telemetry", + 4096, + nullptr, + 2, + nullptr, + 0) != pdPASS) { + ESP_LOGE(kTag, "Failed to create telemetry task on CPU0"); + return; + } +#else + ESP_LOGW(kTag, "Live/P2 transmission disabled; Ethernet bring-up only"); +#endif +} diff --git a/embedded/esp32p4_smv_injector/main/ethernet_port.c b/embedded/esp32p4_smv_injector/main/ethernet_port.c new file mode 100644 index 000000000..93000d9a2 --- /dev/null +++ b/embedded/esp32p4_smv_injector/main/ethernet_port.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ethernet_port.h" + +#include "esp_err.h" +#include "esp_eth_mac_esp.h" +#include "esp_log.h" + +static const char *TAG = "ar_smv_eth"; + +// Bench-validated ESP32-P4 Ethernet-board RMII wiring. +// REF_CLK is supplied by the onboard PHY as a 50 MHz input to ESP32-P4. +enum { + AR_PHY_ADDRESS = 1, + AR_PHY_RESET_GPIO = 51, + AR_MDC_GPIO = 31, + AR_MDIO_GPIO = 52, + AR_RMII_REF_CLK_GPIO = 50, + AR_RMII_TX_EN_GPIO = 49, + AR_RMII_TXD0_GPIO = 34, + AR_RMII_TXD1_GPIO = 35, + AR_RMII_CRS_DV_GPIO = 28, + AR_RMII_RXD0_GPIO = 29, + AR_RMII_RXD1_GPIO = 30, +}; + +esp_eth_handle_t ar_esp32p4_eth_init(void) +{ + eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); + mac_config.flags |= ETH_MAC_FLAG_PIN_TO_CORE; + + eth_esp32_emac_config_t emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG(); + emac_config.interface = EMAC_DATA_INTERFACE_RMII; + emac_config.smi_gpio.mdc_num = AR_MDC_GPIO; + emac_config.smi_gpio.mdio_num = AR_MDIO_GPIO; + emac_config.clock_config.rmii.clock_mode = EMAC_CLK_EXT_IN; + emac_config.clock_config.rmii.clock_gpio = AR_RMII_REF_CLK_GPIO; + emac_config.emac_dataif_gpio.rmii.tx_en_num = AR_RMII_TX_EN_GPIO; + emac_config.emac_dataif_gpio.rmii.txd0_num = AR_RMII_TXD0_GPIO; + emac_config.emac_dataif_gpio.rmii.txd1_num = AR_RMII_TXD1_GPIO; + emac_config.emac_dataif_gpio.rmii.crs_dv_num = AR_RMII_CRS_DV_GPIO; + emac_config.emac_dataif_gpio.rmii.rxd0_num = AR_RMII_RXD0_GPIO; + emac_config.emac_dataif_gpio.rmii.rxd1_num = AR_RMII_RXD1_GPIO; + + esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&emac_config, &mac_config); + if (mac == NULL) { + ESP_LOGE(TAG, "Failed to create ESP32-P4 EMAC"); + return NULL; + } + + eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); + phy_config.phy_addr = AR_PHY_ADDRESS; + phy_config.reset_gpio_num = AR_PHY_RESET_GPIO; + + esp_eth_phy_t *phy = esp_eth_phy_new_ip101(&phy_config); + if (phy == NULL) { + ESP_LOGE(TAG, "Failed to create onboard RMII PHY"); + mac->del(mac); + return NULL; + } + + esp_eth_config_t driver_config = ETH_DEFAULT_CONFIG(mac, phy); + esp_eth_handle_t handle = NULL; + const esp_err_t install_result = esp_eth_driver_install(&driver_config, &handle); + if (install_result != ESP_OK) { + ESP_LOGE(TAG, "Ethernet driver install failed: %s", esp_err_to_name(install_result)); + mac->del(mac); + phy->del(phy); + return NULL; + } + + ESP_LOGI(TAG, + "Ethernet configured: PHY addr=%d MDC=%d MDIO=%d REF_CLK=%d", + AR_PHY_ADDRESS, AR_MDC_GPIO, AR_MDIO_GPIO, AR_RMII_REF_CLK_GPIO); + return handle; +} diff --git a/embedded/esp32p4_smv_injector/main/ethernet_port.h b/embedded/esp32p4_smv_injector/main/ethernet_port.h new file mode 100644 index 000000000..a5e4726eb --- /dev/null +++ b/embedded/esp32p4_smv_injector/main/ethernet_port.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#pragma once + +#include "esp_eth.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Bring up the current ESP32-P4 Ethernet development board's onboard RMII PHY + * and internal EMAC using the bench-validated pin mapping. + * + * The implementation intentionally lives in a C translation unit. ESP-IDF 5.5.x + * exposes several Ethernet default-config macros using C designated initializers; + * keeping that ABI-facing board adapter in C avoids leaking version-sensitive IDF + * initialization details into the portable C++ publisher code. + */ +esp_eth_handle_t ar_esp32p4_eth_init(void); + +#ifdef __cplusplus +} +#endif diff --git a/embedded/esp32p4_smv_injector/sdkconfig.defaults b/embedded/esp32p4_smv_injector/sdkconfig.defaults new file mode 100644 index 000000000..e5908e179 --- /dev/null +++ b/embedded/esp32p4_smv_injector/sdkconfig.defaults @@ -0,0 +1,15 @@ +# ARStack ESP32-P4-ETH SMV Injector defaults. +CONFIG_IDF_TARGET="esp32p4" +CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_COMPILER_OPTIMIZATION_PERF=y +CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 +CONFIG_FREERTOS_HZ=1000 +CONFIG_ETH_ENABLED=y +CONFIG_ETH_USE_ESP32_EMAC=y +CONFIG_AR_SMV_LIVE_TX=y +CONFIG_AR_SMV_P2_REALTIME_50HZ=y +CONFIG_AR_SMV_CANONICAL_VLAN=y +CONFIG_AR_SMV_BROADCAST_MIRROR=y +# CONFIG_AR_SMV_DIAGNOSTIC_PROBES is not set +CONFIG_AR_SMV_CURRENT_RMS_MA=1000 +CONFIG_AR_SMV_VOLTAGE_RMS_MV=57740