EtherCAT.Master (ethercat v0.4.2)

Copy Markdown View Source

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 operational

Recovery 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

server()

@type server() :: :gen_statem.server_ref()

t()

@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

activate()

@spec activate() :: :ok | {:error, term()}

Drive the current session toward its operational target.

await_dc_locked(timeout_ms \\ 5000)

@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.

await_operational(timeout_ms \\ 10000)

@spec await_operational(pos_integer()) :: :ok | {:error, term()}

Wait until the master reaches full operational cyclic runtime.

await_running(timeout_ms \\ 10000)

@spec await_running(pos_integer()) :: :ok | {:error, term()}

Wait until the master reaches a usable running state.

bus()

@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.

configure_slave(slave_name, spec)

@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.

dc_status()

@spec dc_status() ::
  EtherCAT.DC.Status.t()
  | {:error, :not_started | :timeout | {:server_exit, term()}}

Return the current Distributed Clocks runtime status snapshot.

deactivate(target \\ :safeop)

@spec deactivate(:safeop | :preop) :: :ok | {:error, term()}

Retreat the current session to :safeop or :preop while keeping the master and bus runtime alive.

domains()

@spec domains() :: list() | {:error, :not_started | :timeout | {:server_exit, term()}}

Return compact runtime snapshots for configured domains.

Each entry contains {domain_id, live_cycle_time_us, pid}.

last_failure()

@spec last_failure() ::
  map() | nil | {:error, :not_started | :timeout | {:server_exit, term()}}

Return the last retained terminal failure snapshot, if any.

reference_clock()

@spec reference_clock() ::
  {:ok, %{name: atom() | nil, station: non_neg_integer()}} | {:error, term()}

Return the currently selected DC reference clock as %{name, station}.

slaves()

@spec slaves() :: list() | {:error, :not_started | :timeout | {:server_exit, term()}}

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(opts \\ [])

@spec start(keyword()) :: :ok | {:error, term()}

Start the singleton master session with the given startup options.

This is the direct module-level entry point behind EtherCAT.start/1.

state()

@spec state() :: atom() | {:error, :not_started | :timeout | {:server_exit, term()}}

Return the current public master lifecycle state.

stop()

@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.

update_domain_cycle_time(domain_id, cycle_time_us)

@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.