EtherCAT.Bus.Transport behaviour (ethercat v0.4.2)

Copy Markdown View Source

Behaviour for EtherCAT frame transports.

Modelled after :gen_udp's {active, once} pattern: arm once, receive one message containing the EtherCAT payload, repeat.

Implementations

Async receive flow

# After sending a frame, arm for one delivery:
transport_mod.set_active_once(transport)

# In gen_statem handle_event(:info, msg, :awaiting, data):
case transport_mod.match(transport, msg) do
  {:ok, ecat_payload, rx_at, _src_mac} -> # process response
  {:error, reason} -> # transport-level receive failure
  :ignore -> :keep_state_and_data
end

Summary

Callbacks

Close the transport. Returns the struct with the socket set to nil.

Drain buffered frames and cancel any pending select/active registration.

Returns the network interface name, or nil if not applicable.

Decode one process mailbox message from this transport.

Returns a human-readable transport identifier for telemetry and logging.

Open a transport from keyword options.

Returns true when the transport is open.

Re-arm for the next async delivery without draining buffered frames.

Send an EtherCAT payload. Returns monotonic tx timestamp on success.

Arm for exactly one async delivery — analogous to inet:setopts([{active, once}]).

Returns the transport's own source MAC address (6 bytes), or nil.

Types

t()

@type t() :: struct()

Callbacks

close(t)

@callback close(t()) :: t()

Close the transport. Returns the struct with the socket set to nil.

drain(t)

@callback drain(t()) :: :ok

Drain buffered frames and cancel any pending select/active registration.

interface(t)

@callback interface(t()) :: String.t() | nil

Returns the network interface name, or nil if not applicable.

Used by the internal bus link monitor. Returns nil for transports that don't rely on a named OS interface (e.g. UdpSocket).

match(t, msg)

@callback match(t(), msg :: term()) ::
  {:ok, ecat_payload :: binary(), rx_at :: integer(),
   frame_src_mac :: binary() | nil}
  | {:error, reason :: term()}
  | :ignore

Decode one process mailbox message from this transport.

Returns {:ok, ecat_payload, rx_at, frame_src_mac} when the message belongs to this transport and data is ready; {:error, reason} when the message belongs to this transport but receive processing failed; :ignore for all other messages.

frame_src_mac is the 6-byte source MAC address from the Ethernet header of the received frame, or nil for transports that don't expose it (UDP). The redundant link uses this to distinguish cross-over frames (sent by the other port's NIC) from own-port loopback frames.

  • RawSocket: matches {:"$socket", raw, :select, _}, calls recvmsg internally, strips Ethernet headers. The two-step select dance is hidden.
  • UdpSocket: matches {:udp, raw, _ip, _port, data} and returns data inline.

name(t)

@callback name(t()) :: String.t()

Returns a human-readable transport identifier for telemetry and logging.

  • RawSocket: returns the interface name (e.g. "eth0")
  • UdpSocket: returns "host:port" (e.g. "192.168.1.1:34980")

open(keyword)

@callback open(keyword()) :: {:ok, t()} | {:error, term()}

Open a transport from keyword options.

open?(t)

@callback open?(t()) :: boolean()

Returns true when the transport is open.

rearm(t)

@callback rearm(t()) :: :ok

Re-arm for the next async delivery without draining buffered frames.

Unlike set_active_once/1, this preserves any frames already in the socket buffer. Used by the bus after an idx-mismatch so that a legitimate response sitting behind a rogue frame is not lost.

  • RawSocket: self-sends a synthetic select notification so match/2 re-enters and reads the next buffered frame.
  • UdpSocket: delegates to set_active_once/1 (no drain issue).

send(t, ecat_payload)

@callback send(t(), ecat_payload :: binary()) ::
  {:ok, tx_at :: integer()} | {:error, term()}

Send an EtherCAT payload. Returns monotonic tx timestamp on success.

The transport is responsible for any outer framing (Ethernet headers, UDP/IP headers). The payload is the output of Bus.Frame.encode/1.

set_active_once(t)

@callback set_active_once(t()) :: :ok

Arm for exactly one async delivery — analogous to inet:setopts([{active, once}]).

After calling this, the next received frame will be delivered as a message to the calling process. Use match/2 to decode it.

  • RawSocket: calls :socket.recvmsg(:nowait) to register the kernel select
  • UdpSocket: calls :inet.setopts([{:active, :once}])

src_mac(t)

@callback src_mac(t()) :: <<_::48>> | nil

Returns the transport's own source MAC address (6 bytes), or nil.

Used by the redundant link to identify whether a received frame originated from this port's NIC or crossed over from the other port. Returns nil for transports without MAC addresses (e.g. UDP).