# `EtherCAT.Slave`
[🔗](https://github.com/sid2baker/ethercat/blob/main/lib/ethercat/slave.ex#L1)

EtherCAT State Machine lifecycle for one physical slave device.

`EtherCAT.Slave` is the public boundary for one named slave. Each slave owns
its AL-state transitions, mailbox setup, process-data registration, health
polling, reconnect handling, and signal delivery.

One slave process is started per configured name and registered under
`{:slave, name}`. The public API is typically driven by the master, but
direct slave-local calls are available here for introspection, output
staging, SDO traffic, and controlled retries.

## State transitions

```mermaid
stateDiagram-v2
    state "INIT" as init
    state "BOOTSTRAP" as bootstrap
    state "PREOP" as preop
    state "SAFEOP" as safeop
    state "OP" as op
    state "DOWN" as down
    [*] --> init
    init --> preop: auto-advance succeeds
    init --> init: auto-advance retries
    init --> bootstrap: bootstrap is requested
    init --> safeop: SAFEOP request path succeeds
    init --> op: OP request path succeeds
    bootstrap --> init: INIT is requested
    preop --> safeop: SAFEOP is requested
    preop --> op: OP is requested
    preop --> init: INIT is requested or AL health regresses to INIT
    preop --> bootstrap: AL health regresses to BOOTSTRAP
    preop --> down: health poll sees bus loss or zero WKC
    safeop --> op: OP is requested
    safeop --> preop: PREOP is requested or AL health regresses to PREOP
    safeop --> init: INIT is requested or AL health regresses to INIT
    safeop --> bootstrap: AL health regresses to BOOTSTRAP
    safeop --> down: health poll sees bus loss or zero WKC
    op --> safeop: SAFEOP is requested or AL health retreats
    op --> preop: PREOP is requested
    op --> init: INIT is requested
    op --> down: health poll sees bus loss or zero WKC
    down --> preop: link/position probe succeeds and PREOP rebuild succeeds
    down --> init: reconnect rebuild retries from INIT
```

# `server`

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

# `t`

```elixir
@type t() :: %EtherCAT.Slave{
  active_latches: list() | nil,
  bus: EtherCAT.Bus.server() | nil,
  config: EtherCAT.Slave.Config.t() | nil,
  configuration_error: term() | nil,
  dc_cycle_ns: non_neg_integer() | nil,
  driver: module() | nil,
  error_code: non_neg_integer() | nil,
  esc_info: map() | nil,
  health_poll_ms: pos_integer() | nil,
  identity: map() | nil,
  latch_names: map(),
  latch_poll_ms: pos_integer() | nil,
  mailbox_config: map() | nil,
  mailbox_counter: non_neg_integer() | nil,
  name: atom() | nil,
  output_domain_ids_by_sm: map() | nil,
  output_sm_images: map() | nil,
  position: non_neg_integer() | nil,
  process_data_request: :none | {:all, atom()} | [{atom(), atom()}] | nil,
  signal_registrations: map() | nil,
  signal_registrations_by_sm: map() | nil,
  sii_pdo_configs: list() | nil,
  sii_sm_configs: list() | nil,
  startup_retry_count: non_neg_integer(),
  startup_retry_phase: atom() | nil,
  station: non_neg_integer() | nil,
  subscriber_refs: %{optional(pid()) =&gt; reference()},
  subscriptions: map() | nil,
  sync_config: EtherCAT.Slave.Sync.Config.t() | nil
}
```

# `configure`

```elixir
@spec configure(
  atom(),
  keyword()
) :: :ok | {:error, term()}
```

Apply runtime configuration updates for one slave.

# `download_sdo`

```elixir
@spec download_sdo(atom(), non_neg_integer(), non_neg_integer(), binary()) ::
  :ok | {:error, term()}
```

Download one CoE SDO value to the slave mailbox.

# `error`

```elixir
@spec error(atom()) ::
  non_neg_integer()
  | nil
  | {:error, :not_found | :timeout | {:server_exit, term()}}
```

Return the current AL error code, if the slave has reported one.

# `identity`

```elixir
@spec identity(atom()) ::
  map() | nil | {:error, :not_found | :timeout | {:server_exit, term()}}
```

Return the cached identity information for the slave, if available.

# `info`

```elixir
@spec info(atom()) ::
  {:ok, map()} | {:error, :not_found | :timeout | {:server_exit, term()}}
```

Return a detailed runtime snapshot for the slave.

# `read_input`

```elixir
@spec read_input(atom(), atom()) :: {:ok, {term(), integer()}} | {:error, term()}
```

Read the latest decoded value for one registered input signal.

Returns `{:error, :not_ready}` before the first valid domain refresh and
`{:error, {:stale, details}}` once the cached sample is older than the
domain freshness window.

# `request`

```elixir
@spec request(atom(), atom()) :: :ok | {:error, term()}
```

Request a target AL state for the named slave.

The runtime may walk the required intermediate path internally before the
request completes.

# `retry_preop_configuration`

```elixir
@spec retry_preop_configuration(atom()) :: :ok | {:error, term()}
```

Retry a retained PREOP configuration failure for one slave.

# `state`

```elixir
@spec state(atom()) ::
  atom() | {:error, :not_found | :timeout | {:server_exit, term()}}
```

Return the current slave AL state.

# `subscribe`

```elixir
@spec subscribe(atom(), atom(), pid()) ::
  :ok
  | {:error,
     {:not_registered, atom()} | :not_found | :timeout | {:server_exit, term()}}
```

Subscribe `pid` to a registered process-data signal or configured latch name.

Signal updates arrive as `{:ethercat, :signal, slave_name, signal_name, value}`.
Latch edges arrive as `{:ethercat, :latch, slave_name, latch_name, timestamp_ns}`.

# `upload_sdo`

```elixir
@spec upload_sdo(atom(), non_neg_integer(), non_neg_integer()) ::
  {:ok, binary()} | {:error, term()}
```

Upload one CoE SDO value from the slave mailbox.

# `write_output`

```elixir
@spec write_output(atom(), atom(), term()) :: :ok | {:error, term()}
```

Stage an output value into the slave's process image for the next domain
cycle.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
