Repository navigation
SPoE fieldbus driver - #68
Merged
Merged
Conversation
Running a script under hil/ writes __pycache__ next to it. Only clients/python ignored it, so the root ignores it for every directory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closed
38 tasks
The SPoE driver talks to each drive over one TCP connection. This adds the frame codec and that connection, with no FieldbusDriver yet. A reply is matched to its request by sequence id and type. A reply that does not match is a late answer to a request that timed out, so it is discarded and never returned for a later request. Received bytes stay in a buffer between requests, so a late reply does not break the framing of the next one. A header that claims more than 512 bytes closes the connection, because the firmware never sends one. A failed request reports a timeout, a closed connection or an invalid request. The driver needs that difference: a closed connection means the device is lost, and a timeout leaves the connection usable. The drive answers one request at a time, so requests take turns behind one mutex. The control plane and the process-data exchange will share it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SpoeFieldbusDriver reaches each drive over its own SPoE connection. It scans, reads the identity, reads and changes the state, reads and writes SDOs, and reads the object dictionary. Process data and file transfer come later. A position is the place of an address in the configured list. A drive that does not answer keeps its position with no state known, so the positions after it do not shift. The bulk parameter list is not trusted alone. The status of each middle packet comes from an uninitialised variable in the firmware, and when it reads as BUSY the packet's entries are lost. So the list is checked against the index list, and every gap is filled one description at a time. Only an entry the drive never answers counts as missing, so a lossy read is never cached. In Monitor mode a PLC owns the drive's state. FieldbusDriver gains stateChangeRefusal, so DeviceManager refuses the change with that reason and POST /api/devices/state answers 409, instead of a vague "target not reached". The config file and POST /api/init share one fieldbus block: ipAddresses and a spoe block with mode, watchdogMs and port. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SPoE is TCP, one request and one reply at a time, so a round trip cannot run on the RT thread. Each drive gets an exchange thread. The RT thread hands it the newest outputs through a triple buffer and takes one input frame per cycle from a single-producer single-consumer queue, so neither side waits. The exchange thread starts the next request as soon as the previous one answers. That keeps the drive's input buffer small, and the queue paces its batched frames to the RT cycle, so monitoring sees every frame in order. A frame cut at the 500-byte reply limit is joined to the rest, and the framing heals itself if the drive discarded that rest. In Monitor mode a PLC owns the state and nothing calls transitionToState, which is where the process image is published. followObservedStates re-maps when a drive enters SAFE-OP or OP and tears down when none exchanges. A state-follower thread in main.cc repeats it while the driver refuses state changes, because DeviceManager owns no thread. The driver clears the PDO mode on stop and on destruction, so a clean shutdown does not trip a Control-mode drive's watchdog. A state change is judged by the state in the reply, because firmware versions disagree on the status. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The SPoE driver reads and writes files, so the existing firmware installation runs over SPoE. A file write sends the name, then 500-byte chunks, and an empty file still sends its last packet. File packets get 30 s, because the firmware holds its reply while the SoC is busy and erases 512 KB of flash before it answers the first COM-firmware packet. Over EtherCAT, leaving BOOT applies the firmware. Over SPoE an explicit FIRMWARE_UPDATE message does, and it restarts the drive. The installation gains an activate-firmware step for it, backed by FieldbusDriver::activateFirmware, which does nothing by default. The firmware answers before it resets, so the driver waits for the drive to stop answering and then to answer again, rather than sleeping. The first packet of com_firmware.bin in BOOT answers 0x00 for success: the firmware returns what storage_prepare_for_writing returned. The driver accepts that for this one packet. SPoE has no SII, so the installation records that and continues. A device-locate procedure blinks the LEDs where the transport can, and stops them at the end or on cancel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SPoE reaches each drive without an EtherCAT Slave Controller, so the routes that need SII, ESC registers, DC or the ESC diagnostics answer 409 with the reason, instead of a driver error as a 500. Each device reports supportsEsc and supportsLocate, so a client knows which pages apply without trying them. GET /api/process-image reports droppedInputFrames: the input frames a SPoE drive returned that the RT loop could not take in time. The Console's Fieldbus page enables SPoE. It takes one IP address per drive and asks for Monitoring or Control mode, with what each one means. The pages that need an ESC say that SPoE does not provide them, and send no requests. The Process Image page warns when input frames were dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The control-mode SPoE concurrency test re-mapped back to back. A re-map holds the image unpublished for most of its duration, and on the macOS runner one re-map took 100 to 150 ms. The cycle guard was falsy for the whole run, so the test counted zero cycles and failed. A 10 ms pause after each re-map gives the cycle a window to run in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drives with firmware older than the SPoE watchdog setting report protocol version 1.0. Its server information carries no PDO mode, and it has no WATCHDOG_TIMEOUT message. The driver refused these drives, because it needed version 1.2 and three bytes of server information. The driver now accepts 1.0 and 1.2 and keeps the version of each drive. In Control mode it sends WATCHDOG_TIMEOUT to a 1.2 drive only. A 1.0 drive keeps the watchdog that its firmware fixes, and the driver logs that spoe.watchdogMs has no effect. After a firmware update the driver reads the version again. The fake server answers as 1.0 firmware on request, and tests cover identification and Control-mode setup against it. Tested on an Integro with firmware v5.6.0-beta.0+26: identification, object dictionary, Monitor process data, file reads, and Control mode up to Operation Enabled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the firmware refuses the first packet of a file write, it replies in the last-packet state and puts the FoE error in the code. That error can be 0, which is SQI_BRG_FOE_ERR_UNDEFINED. The driver accepted a 0 code for com_firmware.bin, so it read such a refusal as success. The driver now treats a first reply in the last-packet state as a refusal, for every file. A reply in the first-packet state succeeds when its code is ACK or 0. In BOOT the COM firmware answers with the return value of storage_prepare_for_writing, and 0 is success. The fake server can refuse a file write, and a test covers the refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An SPoE drive starts in OP. The process image is published only when transitionToState brings a device to SAFE-OP or OP, so in Control mode no image was built and Motion Master did not exchange process data with a drive that was already in OP. The scan now reads the device states. When a device is in SAFE-OP or OP, the scan reads its object dictionary and publishes the image. A failure of that step is logged, and the scan still succeeds. A SOEM scan leaves every slave in INIT, so nothing changes for EtherCAT. The image binds each entry to its parameter cell when it is built. So the dictionary is read before the image, both at the scan and in the Monitor-mode state follower. Without it, the values arrive and nothing decodes them until the next re-map. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The release ships SPoE for testing on hardware that reaches SAFE-OP, which this bench does not have. The changelog tells the testers what works and what to avoid. NEXTGEN.md keeps the hardware findings and the list of untested items, known problems and later work. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
markosankovic
marked this pull request as ready for review
October 10, 2026 11:24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #36.
The SPoE fieldbus driver, built in slices. This PR collects every commit for #36.
Already on
mainbefore this branch existed:Planned slices:
main.cc(6d0bf06)swagger.yml, the generated client, the Console, and the dropped input-frame count in bus health (fe64695)Before merge: hardware checks on an Integro with Ethernet firmware
These need a drive, and #36 lists each one:
hil/spoe/second_client.py: a second client gets no answer, and the first keeps the drive🤖 Generated with Claude Code