Public runtime API for the EtherCAT protocol boundary.
start/1 returns an opaque %EtherCAT.Session{}. Every runtime operation
requires that session, so a caller can never cross a stop/start boundary by
resolving through the current singleton runtime:
{:ok, session} = EtherCAT.start(backend: {:raw, %{interface: "eth0"}})
:ok = EtherCAT.await_ready(session)
{:ok, :preop_ready} = EtherCAT.state(session)
{:ok, slaves} = EtherCAT.Diagnostics.slaves(session)
:ok = EtherCAT.stop(session)With no explicit slave configuration, discovery holds the ring in PREOP;
it does not start cyclic I/O. For cyclic operation, supply ordered
EtherCAT.Slave.Config entries and EtherCAT.Domain.Config entries, then use
await_operational/2. See the getting-started guide or the
no-hardware walkthrough in EtherCAT.Simulator.
A stopped or replaced session returns {:error, :stale_session}.
Normal applications interact with EtherCAT through lifecycle operations, coherent process-data samples, static slave descriptions, protocol status, notifications, and explicit protocol writes.
A %EtherCAT.Sample{} is a coherent decoded observation from one domain
cycle. It is protocol truth, not projected machine state. Different domains
do not share a consistency boundary.
Semantic commands, machine state, and machine events belong above this
library in a separate integration such as an Entity.Provider.
Specialist APIs live under:
EtherCAT.Provisioningfor PREOP configuration, activation, and SDO trafficEtherCAT.Diagnosticsfor DC, slave, domain, and topology inspectionEtherCAT.Signalsfor signal and latch subscriptionsEtherCAT.Driverfor protocol/device driver authorsEtherCAT.Simulatorfor testing and simulator workflows
Host applications must supervise EtherCAT.Runtime before calling this API.
start/1 and stop/1 control one session inside that host-owned runtime;
they do not boot the supervision tree themselves.
Summary
Functions
Block until session reaches operational cyclic runtime.
Wait for PREOP readiness, completed deactivation, or operational cyclic runtime.
Return the static protocol description for one slave.
Return static protocol descriptions for the session.
Read a decoded input and its refresh timestamp in host monotonic microseconds.
Return the latest retained sample for one slave and domain.
Return the latest retained domain samples for one slave.
Return configured slave names for the session.
Start a master session and return its opaque identity.
Return the session lifecycle state.
Return current protocol/runtime status for one slave.
Stop exactly session.
Subscribe a process to protocol observations from one slave.
Cancel a protocol subscription. Repeated cancellation is harmless.
Stage one decoded output signal for the session's next domain cycle.
Types
@type description() :: EtherCAT.SlaveDescription.t()
@type domain_id() :: atom()
@type inventory() :: %{optional(slave_name()) => description()}
@type sample_map() :: %{optional(domain_id()) => EtherCAT.Sample.t()}
@type session() :: EtherCAT.Session.t()
@type session_query_result(value) :: {:ok, value} | session_query_error()
@type session_state() ::
:discovering
| :awaiting_preop
| :preop_ready
| :deactivated
| :operational
| :activation_blocked
| :recovering
@type slave_name() :: atom()
@type slave_status() :: EtherCAT.Slave.Status.t()
Functions
@spec await_operational(EtherCAT.Session.t(), pos_integer()) :: :ok | {:error, term()}
Block until session reaches operational cyclic runtime.
@spec await_ready(EtherCAT.Session.t(), pos_integer()) :: :ok | {:error, term()}
Wait for PREOP readiness, completed deactivation, or operational cyclic runtime.
Use await_operational/2 when your application requires cyclic I/O.
@spec describe(EtherCAT.Session.t(), slave_name()) :: {:ok, description()} | {:error, term()}
Return the static protocol description for one slave.
@spec inventory(EtherCAT.Session.t()) :: session_query_result(inventory())
Return static protocol descriptions for the session.
@spec read(EtherCAT.Session.t(), slave_name(), atom()) :: {:ok, {term(), integer()}} | {:error, term()}
Read a decoded input and its refresh timestamp in host monotonic microseconds.
Returns {:error, :not_ready} before the first valid domain refresh and
{:error, {:stale, details}} after the domain freshness window expires. Decode
failures remain errors, not substitute values. Unlike sample/3, this reads
the current domain image with freshness checking rather than a retained
change-driven observation.
@spec sample(EtherCAT.Session.t(), slave_name(), domain_id()) :: {:ok, EtherCAT.Sample.t()} | {:error, term()}
Return the latest retained sample for one slave and domain.
Returns {:error, :not_ready} when no sample is retained for that domain,
including before its first input observation. await_operational/2 alone does
not guarantee a sample is already available. Inspect Sample.errors for
per-signal decode failures and status/2 for current runtime health; a retained
sample is not a liveness guarantee.
@spec samples(EtherCAT.Session.t(), slave_name()) :: {:ok, sample_map()} | {:error, term()}
Return the latest retained domain samples for one slave.
@spec slaves(EtherCAT.Session.t()) :: session_query_result([slave_name()])
Return configured slave names for the session.
@spec start(keyword()) :: {:ok, EtherCAT.Session.t()} | {:error, term()}
Start a master session and return its opaque identity.
The host must already supervise EtherCAT.Runtime. Startup is asynchronous:
use await_ready/2 for PREOP provisioning or await_operational/2 for cyclic I/O.
Options:
:backend(required) — raw, redundant raw, or UDPEtherCAT.Backenddescription:slaves—EtherCAT.Slave.Configentries in physical bus order. Omit or pass[]to assign discovered devices the names:coupler,:slave_1, … and hold them in PREOP. These are positional names, not inferred device types.:domains—EtherCAT.Domain.Configentries, default[]. Domain IDs must exist before slave process-data assignments refer to them.:dc—EtherCAT.DC.Configor keyword options; defaults to%EtherCAT.DC.Config{}. Setnilto disable Distributed Clocks.:base_station— first assigned station address, default0x1000:scan_stable_ms/:scan_poll_ms— stable-count window and discovery poll interval in milliseconds, default1000/100:frame_timeout_ms— optional bus response timeout override in milliseconds; otherwise the runtime derives it from the configured cycle budget
Only one session can be active per BEAM node. This operation assigns station addresses and changes slave state; do not share its interface with another master.
@spec state(EtherCAT.Session.t()) :: session_query_result(session_state())
Return the session lifecycle state.
@spec status(EtherCAT.Session.t(), slave_name()) :: {:ok, slave_status()} | {:error, term()}
Return current protocol/runtime status for one slave.
@spec stop(EtherCAT.Session.t()) :: :ok | {:error, term()}
Stop exactly session.
@spec subscribe(EtherCAT.Session.t(), slave_name(), pid()) :: {:ok, reference(), slave_status(), sample_map()} | {:error, term()}
Subscribe a process to protocol observations from one slave.
Registration and the returned status/sample values share the slave process's
serialization boundary. Subsequent observations arrive as
{:ethercat, ref, payload} messages, where payload is an EtherCAT.Sample
or EtherCAT.Notification. Each registration has a unique reference.
Delivery is push-based with no backpressure or dropping. Subscribers must keep
up; use samples/2 to poll the latest values when every cycle is unnecessary.
Registrations end when the subscriber or slave exits.
@spec unsubscribe(EtherCAT.Session.t(), slave_name(), reference()) :: :ok | {:error, term()}
Cancel a protocol subscription. Repeated cancellation is harmless.
Already queued messages retain their reference and may still be received.
@spec write(EtherCAT.Session.t(), slave_name(), atom(), term()) :: :ok | {:error, term()}
Stage one decoded output signal for the session's next domain cycle.
:ok means the driver encoded the value and the runtime staged it. It is not
an acknowledgement from the device or proof of physical actuation. Subsequent
writes can replace the staged value before a cycle transmits it.