# `BB.Actuator.Server`
[🔗](https://github.com/beam-bots/bb/blob/main/lib/bb/actuator/server.ex#L5)

Wrapper GenServer for actuator callback modules.

This module manages the lifecycle of user-defined actuator modules, handling:
- Parameter reference resolution at startup
- Subscription to parameter changes
- Subscription to the actuator's own command topic
- Delegation of GenServer callbacks to user module
- Automatic safety registration

User modules implement the `BB.Actuator` behaviour and define callbacks.
This server wraps them, providing the actual GenServer implementation.

## The inbound command pipeline

Commands reach an actuator by three transports - published to
`[:actuator | path]`, cast via `BB.Process.cast/3`, or called via
`BB.Process.call/4`. All three converge here and pass through the same
checks before the driver sees them:

1. The actuator must accept the payload — see
   `c:BB.Actuator.command_payloads/1`. A driver is never handed a command it
   didn't declare, so it can't be crashed by one it has no clause for.
2. The robot must be armed.
3. The command must carry the current arm epoch — see `BB.Safety.epoch/1`.
   `BB.Actuator`'s send functions stamp it; a command built by hand and
   delivered straight to an actuator carries none and is refused. This is
   what stops a command outliving the arming session that authorised it,
   which the armed check alone cannot see.
4. The payload is translated from joint-space into motor-space using the
   joint's transmission.

The driver then receives it in `c:BB.Actuator.handle_command/2`, and its
reply is routed back to whichever transport delivered the command. Messages
from topics the driver subscribed to itself are not part of this pipeline -
they reach `c:BB.Actuator.handle_info/2` untouched.

## Reporting a refusal

Only the call transport can answer its caller directly. A cast may name a
process to be told about a refusal, which `BB.Actuator`'s
`reply_on_reject?: true` uses: the command arrives as
`{:command, message, reply_to}`, and a refusal sends

    {:bb, :command_rejected, actuator_name, command_id, error}

to `reply_to`. `nil` there means nobody is listening, which is the case for
every published command - the topic has no one caller to answer.

# `t`

```elixir
@type t() :: %BB.Actuator.Server{
  actuator_name: atom() | nil,
  bb: %{robot: module(), path: [atom()]},
  callback_module: module(),
  command_payloads: [module()],
  command_topic: [atom()],
  joint: map() | nil,
  joint_name: atom() | nil,
  param_subscriptions: %{required([atom()]) =&gt; atom()},
  raw_opts: keyword(),
  resolved_opts: keyword(),
  transmission: BB.Transmission.t() | nil,
  transmission_subscriptions: %{required(atom()) =&gt; [atom()]},
  user_state: term()
}
```

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

---

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