Master orchestrates startup, activation, deactivation, and runtime recovery for the local EtherCAT session.
EtherCAT.Master is the public boundary for the master lifecycle. It owns
the singleton session exposed through EtherCAT.state/0, while internal
helpers own bus discovery, slave bring-up, activation, deactivation,
recovery, and status projection.
Before the master reports :preop_ready or starts OP activation, it
quiesces the bus. That extra drain window keeps late startup traffic from
leaking into the first public mailbox/configuration exchange or the first OP
transition datagrams.
Lifecycle states
:idle- no active session:discovering- scanning the bus, counting slaves, assigning stations, and preparing startup:awaiting_preop- waiting for configured slaves to reach PREOP:preop_ready- all configured slaves reached PREOP and the session is ready for activation or dynamic configuration:deactivated- the session stays live below OP on purpose:operational- cyclic runtime is active; non-critical slave-local faults may still be tracked:activation_blocked- the desired runtime target was not fully reached:recovering- critical runtime faults are being healed
Startup sequencing
sequenceDiagram
autonumber
participant App
participant Master
participant Bus
participant DC
participant Domain
participant Slave
App->>Master: start/1
Master->>Bus: count slaves, assign stations, verify link
opt DC is configured
Master->>DC: initialize clocks
end
Master->>Domain: start domains in open state
Master->>Slave: start slave processes
Slave->>Bus: reach PREOP through INIT, SII, and mailbox setup
Slave->>Domain: register PDO layout
Slave-->>Master: report ready at PREOP
opt activation is requested and possible
opt DC runtime is available
Master->>DC: start runtime maintenance
end
Master->>Domain: start cyclic exchange
opt DC lock is required
Master->>DC: wait for lock
end
Master->>Slave: request SAFEOP
Master->>Slave: request OP
end
Master-->>App: state becomes preop_ready, activation_blocked, or operationalRecovery model
Domains, slaves, and DC report runtime faults back to the master. Critical
faults move the session into :recovering; slave-local non-critical faults
can remain visible while the master stays :operational.
stateDiagram-v2
[*] --> idle
idle --> discovering: start/1
discovering --> awaiting_preop: configured slaves are still pending
discovering --> idle: startup fails or stop/0
awaiting_preop --> preop_ready: all slaves reached PREOP, no activation requested
awaiting_preop --> operational: all slaves reached PREOP and activation succeeds
awaiting_preop --> activation_blocked: activation is incomplete
awaiting_preop --> idle: timeout, fatal startup failure, or stop/0
preop_ready --> operational: activate/0 succeeds
preop_ready --> activation_blocked: activate/0 is incomplete
preop_ready --> recovering: critical runtime fault
preop_ready --> idle: stop/0 or fatal subsystem exit
deactivated --> operational: activate/0 succeeds
deactivated --> preop_ready: deactivate to PREOP
deactivated --> activation_blocked: target transition remains incomplete
deactivated --> recovering: critical runtime fault
deactivated --> idle: stop/0 or fatal subsystem exit
operational --> recovering: critical runtime fault
operational --> deactivated: deactivate/0 settles in SAFEOP
operational --> preop_ready: deactivate to PREOP
operational --> idle: stop/0 or fatal subsystem exit
activation_blocked --> operational: activation failures clear and target is OP
activation_blocked --> deactivated: transition failures clear and target is SAFEOP
activation_blocked --> preop_ready: transition failures clear and target is PREOP
activation_blocked --> recovering: runtime faults remain after activation retry
activation_blocked --> idle: stop/0 or fatal subsystem exit
recovering --> operational: critical runtime faults are cleared and target is OP
recovering --> deactivated: critical runtime faults are cleared and target is SAFEOP
recovering --> preop_ready: critical runtime faults are cleared and target is PREOP
recovering --> idle: stop/0 or recovery fails
Summary
Functions
Drive the current session toward its operational target.
Wait until the active DC runtime reports a locked status.
Wait until the master reaches full operational cyclic runtime.
Wait until the master reaches a usable running state.
Return the stable local bus server reference, or nil if the session exists
but the bus subsystem is not currently running.
Apply or replace runtime configuration for one named slave.
Return the current Distributed Clocks runtime status snapshot.
Retreat the current session to :safeop or :preop while keeping the master
and bus runtime alive.
Return compact runtime snapshots for configured domains.
Return the last retained terminal failure snapshot, if any.
Return the currently selected DC reference clock as %{name, station}.
Return compact runtime snapshots for all tracked slaves.
Start the singleton master session with the given startup options.
Return the current public master lifecycle state.
Stop the current master session and tear down its runtime.
Update the live cycle time for one configured domain.
Types
@type server() :: :gen_statem.server_ref()
@type t() :: %EtherCAT.Master{ activatable_slaves: [atom()], activation_failures: %{optional(atom()) => term()}, await_callers: [term()], await_operational_callers: [term()], base_station: non_neg_integer(), bus_ref: reference() | nil, dc_config: EtherCAT.DC.Config.t() | nil, dc_ref: reference() | nil, dc_ref_station: non_neg_integer() | nil, dc_stations: [non_neg_integer()], desired_runtime_target: :preop | :safeop | :op, domain_configs: [EtherCAT.Domain.Config.t()] | nil, domain_refs: %{optional(reference()) => atom()}, frame_timeout_floor_ms: pos_integer(), frame_timeout_override_ms: pos_integer() | nil, last_failure: map() | nil, pending_preop: MapSet.t(atom()), runtime_faults: %{optional(term()) => term()}, scan_poll_ms: pos_integer() | nil, scan_stable_ms: pos_integer() | nil, scan_window: [{integer(), non_neg_integer()}], slave_configs: [EtherCAT.Slave.Config.t()] | nil, slave_count: non_neg_integer() | nil, slave_faults: %{optional(atom()) => term()}, slave_refs: %{optional(reference()) => atom()}, slaves: [map()] }
Functions
@spec activate() :: :ok | {:error, term()}
Drive the current session toward its operational target.
@spec await_dc_locked(pos_integer()) :: :ok | {:error, term()}
Wait until the active DC runtime reports a locked status.
Returns a local master-call error if no DC runtime is currently available.
@spec await_operational(pos_integer()) :: :ok | {:error, term()}
Wait until the master reaches full operational cyclic runtime.
@spec await_running(pos_integer()) :: :ok | {:error, term()}
Wait until the master reaches a usable running state.
@spec bus() :: EtherCAT.Bus.server() | nil | {:error, :not_started | :timeout | {:server_exit, term()}}
Return the stable local bus server reference, or nil if the session exists
but the bus subsystem is not currently running.
@spec configure_slave(atom(), keyword() | EtherCAT.Slave.Config.t()) :: :ok | {:error, term()}
Apply or replace runtime configuration for one named slave.
This is primarily used for dynamic PREOP-first workflows.
@spec dc_status() :: EtherCAT.DC.Status.t() | {:error, :not_started | :timeout | {:server_exit, term()}}
Return the current Distributed Clocks runtime status snapshot.
@spec deactivate(:safeop | :preop) :: :ok | {:error, term()}
Retreat the current session to :safeop or :preop while keeping the master
and bus runtime alive.
Return compact runtime snapshots for configured domains.
Each entry contains {domain_id, live_cycle_time_us, pid}.
Return the last retained terminal failure snapshot, if any.
@spec reference_clock() :: {:ok, %{name: atom() | nil, station: non_neg_integer()}} | {:error, term()}
Return the currently selected DC reference clock as %{name, station}.
Return compact runtime snapshots for all tracked slaves.
Each entry includes the configured name, station, server reference, live pid, and any currently tracked slave-local fault.
Start the singleton master session with the given startup options.
This is the direct module-level entry point behind EtherCAT.start/1.
Return the current public master lifecycle state.
@spec stop() :: :ok | :already_stopped | {:error, :timeout | {:server_exit, term()}}
Stop the current master session and tear down its runtime.
Returns :already_stopped when no local master process is running.
@spec update_domain_cycle_time(atom(), pos_integer()) :: :ok | {:error, term()}
Update the live cycle time for one configured domain.
This does not mutate the stored startup config; it updates the running domain process only.