butt-head 0.2.0

A no_std button input processing library for embedded systems
Documentation
# butt-head

[![Platform](https://img.shields.io/badge/platform-no_std-blue)](https://github.com/HybridChild/butt-head)
[![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-green)](https://github.com/HybridChild/butt-head#license)

A `no_std` Rust library for processing button inputs in embedded systems. Transforms clean boolean pin states into button gesture events through a configurable state machine. Pure logic — no I/O, no HAL, no interrupts. The button counterpart to [pot-head](https://crates.io/crates/pot-head).

## Scope

**butt-head** handles gesture recognition: single and multi-click detection, hold detection, and multi-click sequences. It operates on clean boolean pin states and does **not** perform debouncing. Debounce is a hardware-level concern that depends on sampling rate and electrical characteristics — it belongs in your HAL or input driver, before the state reaches this library.

## What You Get

Feed in a pin state and a timestamp. Get back a structured event and a hint for when to call again.

```rust
let result = button.update(pin.is_high(), now());

match result.event {
    Some(Event::Click { count: 1 })                   => single_click(),
    Some(Event::Click { count: 2 })                   => double_click(),
    Some(Event::Hold { clicks_before: 0, level: 0 })  => hold_started(),
    Some(Event::Hold { clicks_before: 1, .. })        => click_then_hold(),
    Some(Event::Press { .. })                         => led_on(),
    Some(Event::Release { duration, .. })             => led_off(),
    _ => {}
}
```

Match on exactly the gestures you care about and ignore the rest. All gesture semantics are handled for you:

- A double-click fires a single `Click { count: 2 }` — never two separate single-clicks.
- A long press fires `Hold` events — never a `Click`.
- Click-then-hold is distinguished from plain hold via `clicks_before`.

For multi-button combos, the library provides a set of coordination primitives:

- `Event::Press { at }` carries the press timestamp, letting you compare press times across buttons to detect simultaneous presses.
- `Event::Release { click_follows, .. }` tells you whether a `Click` event will follow, so you can decide whether to suppress it (e.g. when the release was part of a combo).
- `cancel_pending_click()` cancels the pending `Click` when called from a `Release { click_follows: true }` handler — transitions the state machine back to `Idle` with no click emitted.
- `is_pressed()` and `pressed_duration(now)` let you query button state directly at any time without waiting for an event.

See [`examples/stm32f0-embassy`](examples/stm32f0-embassy/) (`dual_button` binary) for a complete two-button coordination example using these primitives.

## Configuration

```rust
static CONFIG: Config<MyDuration> = Config {
    active_low: true,                              // pin low = pressed
    click_timeout: MyDuration::from_millis(300),   // multi-click window
    hold_delay: MyDuration::from_millis(500),      // time until first Hold fires
    hold_interval: MyDuration::from_millis(200),   // time between subsequent Holds
    max_click_count: None,                         // None = always wait for click_timeout
};

let mut button = ButtHead::new(&CONFIG);
```

Config lives as a `&'static` reference — zero runtime overhead, sits in flash on embedded targets.

`max_click_count` lets you short-circuit the `click_timeout` wait:

- `None` — always wait for `click_timeout` to expire before emitting (default).
- `Some(1)` — emit `Click` on every release immediately, with no timeout wait.
- `Some(n)` — emit immediately once the n-th click in a sequence lands.

## Events

| Event | When it fires |
| ----- | ------------- |
| `Press { at }` | Immediately on every press edge; `at` is the timestamp of the press |
| `Release { duration, click_follows }` | Immediately on every release edge; `click_follows` is `true` when a `Click` event will follow (i.e. no hold was emitted), `false` on a hold-release |
| `Click { count }` | After `click_timeout` with no further press, or immediately when `max_click_count` is reached; `count` reflects multi-clicks |
| `Hold { clicks_before, level }` | Repeatedly while held; `level` increments on each repeat |

## Power-Efficient Scheduling

Every call to `update()` returns a `ServiceTiming` hint telling you exactly when to call again:

```rust
match result.next_service {
    ServiceTiming::Delay(d) => timer.set_alarm(d),   // timer-driven wakeup
    ServiceTiming::Idle     => wait_for_interrupt(), // sleep until pin changes
    ServiceTiming::Immediate => {}                   // call again immediately
}
```

During idle your firmware sleeps until a pin interrupt fires. During a gesture the timer wakes you up at the exact moment the next event could fire. No polling loops, no wasted CPU cycles.

## Works Everywhere

**butt-head** is HAL-agnostic. Integrate it by implementing two small traits — `TimeDuration` and `TimeInstant` — for your platform's time types. See [`examples/`](examples/README.md) for complete integrations with `std::time`, STM32 SysTick, and Embassy.

## Feature Flags

| Feature | What it enables |
| ------- | --------------- |
| `defmt` | `defmt::Format` on all public types for structured RTT logging |

## Examples

| Example | Description |
| ------- | ----------- |
| [`examples/native`]examples/native | Desktop demo using `std::time` and `crossterm` |
| [`examples/stm32f0`]examples/stm32f0 | Bare-metal STM32F0 with SysTick timer |
| [`examples/stm32f0-embassy`]examples/stm32f0-embassy | STM32F0 with Embassy async/await (`single_button` and `dual_button` binaries) |

## License

Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or [MIT License](LICENSE-MIT) at your option.