Repository navigation
feat(ethercat): run the EtherCAT master as EtherDOG, a supervised process [RTOP-296] - #205
Conversation
…cess The SOEM plugin that ran inside plc_main is replaced by a thin client. The master itself is EtherDOG, started and restarted by the webserver and driven over its documented control socket and per-cycle datagrams. - ethercat plugin: joins EtherDOG's layout with ethercat_iomapping.json, relays one frame each way per bus cycle (journal for %I, image_lock for %Q) and reconnects on its own if EtherDOG restarts - webserver: etherdog_manager.py (spawn, supervise, token, session file, busconfig delivery), discovery routes talk to EtherDOG directly, and an upload carrying the old single ethercat.json is rejected - install.sh builds EtherDOG into build/etherdog; the SOEM submodule and the in-process master sources are gone Refs: RTOP-296 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
First runtime with the EtherCAT master in EtherDOG; editors emit the split EtherCAT configuration from this version on. Refs: RTOP-296 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
marconetsf
left a comment
There was a problem hiding this comment.
[required] Same as #203: Requirements Gathering (RTOP-297) and the Cybersecurity Risk Assessment (RTOP-298) need to be written, second pass included, and linked before this merges to development. AC9–AC12 are also still open.
[minor] Test coverage: nothing covers frame parsing in etherdog_link.c, relay reconnect/stop, _upload_has_legacy_ethercat through the real handle_upload_file path, or the apply_busconfig ordering. CI doesn't build the C code or run Ceedling, so test_ethercat_iomap.c and test_ethercat_iec_location.c are only verified locally.
[question] Two things on the EtherDOG side, outside this repo:
- the control socket doesn't check
SO_PEERCRED, which RTOP-296 asks for; - EtherDOG's LICENSE is GPLv3, while the runtime becomes MIT in #203.
Is the licensing split intentional, and is the process boundary what keeps it clean?
[nit] A few small ones:
ethercat_iomap.candetherdog_link.credefine constants instead of includingjournal_buffer.h/etherdog_protocol.h;etherdog_link.c:246silently truncates replies larger thanresponse_size, and the error only shows up later as "not JSON";_write_privatedoesn't useO_NOFOLLOW(low risk, since/run/runtimeis root-owned).
webserver/app.py:616: [minor] Nothing calls etherdog_manager.stop() in the shutdown path. On native installs EtherDOG is left orphaned holding the NIC, since it has no PDEATHSIG. stop() also calls proc.kill() without a wait().
pacman -Sy followed by -S cmake installed a cmake linked against a newer jsoncpp than the one on disk (msys-jsoncpp-27.dll missing). Skip pacman when every package is present, otherwise upgrade with -Syu, and repair a cmake that does not run. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
…indows Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
The runtime runs without EtherDOG; only EtherCAT is lost. The supervisor now: - restarts EtherDOG as soon as it exits instead of after a growing backoff; - disables it at once when it can never start (missing Npcap or another library, bad command line, cannot be executed, not installed); - disables it after 3 exits within 30 s, the runtime's safe-mode policy; - retries on the next program upload. The reason goes in the session file, and the plugin logs it as a warning and skips EtherCAT rather than failing to start. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
Ctrl+C reaches EtherDOG too, and it exited 0 before the supervisor knew the webserver was stopping, so it was restarted. A clean exit is now never restarted, and the webserver stops EtherDOG on shutdown. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
It held the control port, so every new EtherDOG exited and was disabled. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
A new token on every spawn made a leftover EtherDOG impossible to shut down. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
- The bus configuration is staged at upload and loaded by the plugin when the program starts, so the bus and the program always start together. - Control replies have no fixed size (up to 64 MB); a reply too large for execute_command's buffer is an error instead of being cut off. - IEC byte indexes above the 16-bit journal range are rejected, as are duplicate master names and entry counts past the bound array. - Masters with no I/O mapping are ignored with a warning. - Bus and link losses are logged as PLC warnings; the program keeps running. - The relay runs at the bus thread's task_priority. - Control goes over AF_UNIX on every platform, checked by EtherDOG's peer credentials; the token is gone. Leftover EtherDOG processes are killed at startup. - EtherDOG stderr errors are logged at error level until it is up; a killed EtherDOG is reaped; the session file is written with O_NOFOLLOW. - The legacy-upload message names runtime 4.3.0 and Editor 4.3.2. - Tests for the link client, the relay against a fake EtherDOG, the upload path and the new validations; drop the stale SOEM test stubs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
|
Replying to the [question] in the review: Licensing split: yes, it's intentional. EtherDOG is its own program (GPL-3.0-or-later, built on SOEM, in its own repository), and the runtime stays MIT. The process boundary is what keeps them apart:
|
A quote in a message made the line invalid JSON, so the webserver showed it raw at INFO; a message longer than the buffer moved the write past its end. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
Every Windows adapter is \\Device\\NPF_{GUID}, so the Linux-only 15-char check
refused all EtherCAT scans and tests on Windows.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
The start scripts ran Python as a child, so docker stop and a closed console never reached it and EtherDOG was killed without zeroing the outputs. exec the webserver and give SIGTERM/SIGHUP the Ctrl+C shutdown path. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
…e in the driver README The S7comm plugin links Snap7 (LGPLv3+), so its sources take the same license instead of MIT. Review follow-up from #203. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
The EtherCAT relay journals every mapped input each bus cycle, so 2048 inputs per master needed room beside the other plugins. Drain cost follows the writes made, not the capacity (measured: same per-entry cost at 4096 and 40960); the journal grows from 139 KB to 1.4 MB. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
…sleep On the SLM-RP4 plc_main needs a little over a second to open its command socket, so every start logged connection errors before the monitor connected. Retry for up to 10 s and report only if it never comes up. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
…t run start_loop brought the link up once and returned -1 on failure, before the relay thread (the only retry loop) existed; nothing called it again, so a PLC that started before EtherDOG listened kept EtherCAT off until a manual restart. The relay now does the first link_up too. A mapped master that EtherDOG reports as not operational is left unbound with a warning instead of failing every master; the bind fails only when none is operational. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
The Editor stops the PLC before uploading, but other clients may not. The old program kept running through the compile, and its EtherCAT relay restarted the bus with the newly staged configuration. The upload now stops the PLC first and is cancelled if it does not stop. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
The app's module-level runtime manager retries plc_main in the background, and its connect errors landed in these tests' caplog depending on timing. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58
marconetsf
left a comment
There was a problem hiding this comment.
Review of the current head (d9b9840). Inline comments below; one more:
[nit] Commits 92c8767 and 2318921 carry Refs: RTOP-296 in the message body. Commit messages should describe the change without the ticket key; the PR title already links it.
Previous review: most items are resolved (reply buffer, byte-index truncation, duplicate masters, unmapped master, relay priority, first-link retry, upload ordering, stderr level, minimum versions in the message, O_NOFOLLOW, shutdown). Still open: the ETHERDOG_REF pin, and the input-frame sequence/draining, both commented inline.
Plugin: - reject a layout whose process image exceeds one data frame (4096 bytes) or has no size; clamp output collection to the frame buffer - reject a process data entry or an IEC location mapped twice, and a layout that lists one entry in several PDOs - a mapping that cannot bind stops the bus and requests a PLC stop instead of retrying every second - time silence per mapped master; malformed datagrams and unmapped masters no longer affect it - cap the relay priority at 98 and run link setup at SCHED_OTHER - log pthread errors from their return codes Webserver: - EtherDOG errors reach clients as fixed messages per failure kind; the detail is logged - one upload at a time - start() does not start a second supervisor; stop() during the leftover cleanup prevents a later spawn Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Review of Fixed:
Dismissed:
Jira key in the commit bodies ( Tests:
|
The task policy merged from development moves the dispatcher to 98 and the watchdog to 99, so the relay is capped at 97. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Summary
Second part of RTOP-296. The EtherCAT master no longer runs inside
plc_main. It is now EtherDOG, a separate process that the webserver starts and supervises. The runtime talks to it only through EtherDOG's documented protocol: JSON lines on a control socket, and one datagram each way per bus cycle.Stacked on #203. Merge that first; this PR will then retarget to
development.core/src/drivers/plugins/native/ethercat/is now a thin client plugin (etherdog_link.c,ethercat_iomap.c,ethercat_plugin.c).ethercat_iomapping.json, open the data session. The bus and the program always start together.%Ithrough the journal and answers every input frame with%Qread underimage_lock.webserver/etherdog_manager.py:/run/runtime/etherdog.jsonsession file (0600,O_NOFOLLOW): control endpoint, data transport, bus config path, or the disable reason;conf/ethercat_busconfig.jsonand stops the bus;log_runtime.socket.SO_PEERCRED), so the runtime uses no token.scan,test,status,diagnostics,list-interfaces) talk to EtherDOG directly, so they work with no program loaded.plugin_stateis kept for the Editor.conf/ethercat.jsondescribes masters is rejected before anything on the device changes. The empty file older Editors always write is ignored.install.shbuilds EtherDOG intobuild/etherdog.docs/ETHERCAT.md, plus a pointer inCLAUDE.md.The Editor side is DOPE-657 in openplc-editor and openplc-web: runtimes from v4.3.0 get the split files, older or unknown ones the legacy file. This runtime needs
VERSIONv4.3.0 at release for that gate to pick the split format.Ticket
RTOP-296 (epic RTOP-295). Editor side: DOPE-657.
The assessment signatures (author, technical reviewer, project manager) gate the merge. Both documents were written after implementation, with pass 1 and pass 2 consolidated.
How it was tested
On an SLM-RP4 (arm64, PREEMPT_RT) with a Beckhoff EK1818 (8 DI / 4 DO), running an image built from this branch with host networking and
--privileged, as the bootloader runs it:dev:localwith the local-runtime proxy to the device. Clean build and upload, then:Found 2 config files: ['ethercat_busconfig', 'ethercat_iomapping'];0x05/0x06, exactly as the program'sout1..out4logic dictates. 30k+ frames, 0 dropped, 0 watchdog trips.cycle_time_us1000 the wire and the relay both ran at 1000/s; with 2000 both ran at 500/s (period 1947–2060 µs).-9: restarted in about 2 s and the busconfig reloaded. The plugin loggedEtherDOG link restored, andplc_mainkept its PID and stayed RUNNING.tests/test_ethercat_iomap.candtests/test_ethercat_iec_location.c, 9 + 19 passed, built with Unity.plc_main, the client plugin (no SOEM or pcap linkage) andbuild/etherdogall build.After the review fixes (
c388cde, EtherDOG462122a):After the cross-repo review (
560f83b,c1f4bb0), on the SLM-RP4:Windows (MSYS2), on a Parallels VM with no EtherCAT hardware:
install.shbuilds the runtime and EtherDOG.Not tested: EtherCAT on real hardware under Windows.
Known, not from this PR: under Ceedling,
tests/support/debug_handler_mocks.cfails to link any test that doesn't pull inutils.c.Checklist
bash scripts/run-pytest.shCI subset passespre-commit runclean (hooks skipped; they reformat unrelated files)docs/ETHERCAT.md,CLAUDE.md,README.md)docs/pr-reviews/PR_REVIEW_CHECKLIST.md🤖 Generated with Claude Code
https://claude.ai/code/session_01V8oSX7QRK2xfkLVepyMs58