# `CircuitsFT232H.GPIO.Poller`

Per-chip polling GenServer that **emulates** GPIO interrupts.

> ### These are NOT hardware interrupts {: .warning}
>
> The FT232H has no hardware-generated pin-change notifications. We sample
> the pin state on a fixed interval and emit
> `{:circuits_gpio, gpio_spec, timestamp, value}` messages when subscribed
> pins change in the desired direction.
>
> **Pulses shorter than the poll interval (default 10 ms) will be
> missed.** Multiple edges within a single interval are collapsed into one
> notification with the final state.

One Poller per chip is started lazily on the first interrupt subscription
and registered in `CircuitsFT232H.Registry` under `{:poller, device_id}`.
When the last subscription goes away the Poller stops itself.

Each poll tick is one USB round-trip (~1 ms) that reads both the
ADBUS and ACBUS ports in a single MPSSE transaction. All subscribed pins
on the chip are sampled together, so adding more subscribed pins does
*not* increase USB load.

## Tuning the poll interval

The default interval is **10 ms** (~100 Hz sampling). To change
it:

    config :circuits_ft232h, gpio_poll_interval_ms: 5

Lower values reduce the size of pulses you can miss but cost more USB
bandwidth and CPU. Practical floor is ~2 ms (the USB round-trip
time). For mechanical buttons 10-20 ms is plenty; for fast signals
you should look at an actual microcontroller.

## Other notes

  * `:suppress_glitches` is accepted but currently a no-op — there's only
    one sample per poll tick. Filtering glitches shorter than the poll
    interval would require over-sampling, which isn't implemented.
  * Subscriptions are auto-removed if the receiving process dies.

# `subscription`

```elixir
@type subscription() :: %{
  trigger: :rising | :falling | :both,
  receiver: pid(),
  suppress_glitches: boolean(),
  last_value: 0..1
}
```

Per-pin subscription record.

# `t`

```elixir
@type t() :: %CircuitsFT232H.GPIO.Poller{
  device_id: CircuitsFT232H.Device.id(),
  interval_ms: pos_integer(),
  monitors: %{optional(pid()) =&gt; reference()},
  subscriptions: %{optional(CircuitsFT232H.Device.pin()) =&gt; subscription()},
  timer: reference() | nil
}
```

Poller GenServer state.

# `subscribe`

```elixir
@spec subscribe(
  CircuitsFT232H.Device.id(),
  CircuitsFT232H.Device.pin(),
  :rising | :falling | :both,
  keyword()
) :: :ok | {:error, term()}
```

Subscribes the given receiver to edge notifications for `pin` on the chip
identified by `device_id`. Starts the Poller if it isn't running.

`trigger` is one of `:rising`, `:falling`, `:both`.

Options:
  * `:receiver` (default the calling process) — the pid (or registered
    name) that will receive `{:circuits_gpio, gpio_spec, timestamp, value}`
    messages.
  * `:suppress_glitches` (default `true`) — accepted but currently a
    no-op; see the moduledoc.

# `subscriptions`

```elixir
@spec subscriptions(CircuitsFT232H.Device.id()) :: %{
  optional(CircuitsFT232H.Device.pin()) =&gt; subscription()
}
```

Returns the current subscriptions table for inspection in tests.

# `unsubscribe`

```elixir
@spec unsubscribe(CircuitsFT232H.Device.id(), CircuitsFT232H.Device.pin()) :: :ok
```

Removes any active subscription for `pin` on the chip identified by
`device_id`. The Poller stops itself once the last subscription goes away.

# `whereis`

```elixir
@spec whereis(CircuitsFT232H.Device.id()) :: {:ok, pid()} | {:error, :not_started}
```

---

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