# Examples

Flash the firmware first ([`../../docs/FIRMWARE.md`](../../docs/FIRMWARE.md)), plug the
ESP32 in via USB (or pair over Bluetooth — see `wireless/`), then run any
example with [uv](https://docs.astral.sh/uv/) — the local `espbridge` package
from `..` is installed automatically (editable, so library changes apply
immediately):

```sh
cd examples
uv run basics/blink.py
uv run displays/oled_ssd1306.py
uv run wireless/ble_blink.py      # no USB needed
```

Without uv: `pip install -e "..[oled]"` once, then `python basics/blink.py`
(Bluetooth is included in the base install; `[oled]` just adds Pillow).

## basics/ — core peripherals over USB

| example | shows |
|---|---|
| `blink.py` | GPIO output, ping latency |
| `button_interrupt.py` | edge interrupts with debounce → Python callback |
| `watch_threshold.py` | polled (non-ISR) on-device watch: ESP32 pushes an event when a condition trips — analog threshold **or** digital level (or touch / free-heap) — over BLE or USB, no polling |
| `adc_dac.py` | analog read (raw + mV) and true DAC output + cosine generator |
| `pwm_servo.py` | LEDC PWM fade + hobby servo sweep |
| `i2c_scan.py` | I2C bus scan |
| `spi_transfer.py` | full-duplex SPI (flash JEDEC ID) |
| `uart_loopback.py` | second UART for GPS/modems/serial devices (loopback demo) |
| `touch_read.py` | capacitive touch pads: poll, then board-pushed touch events |
| `rtos_concurrency.py` | FreeRTOS task split: fast lane stays ~ms while radio blocks (+ live OLED) |
| `async_fanout.py` | `AsyncBridge`: `await` calls and fan out concurrent I/O with `asyncio.gather` |
| `shared_connection.py` | `espbridge.connect()`: one shared, thread-safe link many threads use — easy integration |

## devices/ — sensors, LEDs, motors, buses (pure-Python drivers)

| example | shows |
|---|---|
| `neopixel_rainbow.py` | WS2812/NeoPixel strip animation over the RMT primitive |
| `dht_read.py` | DHT22/DHT11 temperature + humidity |
| `ds18b20_temp.py` | DS18B20 1-Wire thermometers — several probes on one pin |
| `hcsr04_ping.py` | HC-SR04 ultrasonic distance |
| `ir_remote.py` | receive NEC remote codes and replay them out an IR LED |
| `stepper_move.py` | A4988/DRV8825 stepper with trapezoidal ramps + free-run |
| `can_dump.py` | CAN bus monitor + periodic frame (TWAI, needs a transceiver) |
| `i2s_record.py` | record an I2S MEMS mic to a WAV file on the host |
| `custom_driver.py` | **write your own driver in ~20 lines** — class + `register_driver` → `esp.lm75()` |

These drivers are pure Python over the bridge's primitives — **your own device
is the same pattern, no firmware change**. See
[`../../docs/DRIVERS.md`](../../docs/DRIVERS.md) for writing/registering one
(`esp.<name>(...)`), and `espbridge drivers` to list what's available.

## system/ — on-board storage, sleep, firmware updates

| example | shows |
|---|---|
| `nvs_counter.py` | persistent key/value storage in the ESP32's flash |
| `fs_logger.py` | LittleFS files: append a log on the board, read it back |
| `deep_sleep.py` | deep sleep + timer wake (see chip notes in the main README) |
| `power_profiles.py` | battery vs performance: CPU clock, BLE link profile, heap — one call |
| `ota_update.py` | **reflash the firmware over USB or Bluetooth** — no boot button |
| `mcp_server.py` | run the bridge as an MCP server so an AI agent can drive the board |

## wireless/ — Bluetooth link, ESP-NOW, multiple boards, link speed

| example | shows |
|---|---|
| `ble_blink.py` | **GPIO over Bluetooth — no USB cable.** Default password `espbridge` |
| `ble_scan.py` | BLE advertisement scan (ESP32 as scanner, over USB) |
| `espnow_pair.py` | **two-board ESP-NOW chat, fully wireless** — Bluetooth to the board, ESP-NOW between boards, delivery ACKs |
| `espnow_broadcast.py` | ESP-NOW broadcast over a Bluetooth-connected bridge: one sender, any number of listeners, RSSI per packet |
| `espnow_pingpong.py` | two boards, zero cables: host on two BLE links, ESP-NOW ping/pong between the boards |
| `multi_device.py` | several boards, addressed by persistent name |
| `usb_vs_ble_benchmark.py` | latency + throughput, first over USB then over Bluetooth |
| `stress_test.py` | soak test: sustained, concurrent, multi-radio (Wi-Fi + BLE + ESP-NOW) load |

Bridges advertise as `espbridge_<mac>` (or `espbridge_<mac>_<name>` once you
`espbridge set-name`), so `espbridge scan --ble` finds and labels every board.

The Bluetooth password defaults to `espbridge`; change it by passing it to
`EspBridge.begin("yourpassword")` in the Arduino sketch and reflashing.

## network/ — Wi-Fi through the ESP32 radio

| example | shows |
|---|---|
| `wifi_scan.py` | Wi-Fi scan through the ESP32 radio |
| `wifi_ap.py` | the ESP32 as a Wi-Fi access point (coexists with ESP-NOW and STA) |
| `tcp_through_bridge.py` | join Wi-Fi, HTTP + raw TCP through the ESP32 |
| `udp_through_bridge.py` | UDP datagrams through the ESP32 |

## displays/

| example | shows |
|---|---|
| `oled_ssd1306.py` | SSD1306/SH1106 OLED via `espbridge.drivers.oled.OLED` — auto-handles the common clones, PIL drawing |
| `ble_oled.py` | the same display driven **over Bluetooth** — no COM port at all |

## compat/ — ecosystem integrations (espbridge.compat)

One folder per ecosystem — existing code from these libraries runs unchanged:

| example | ecosystem |
|---|---|
| [`compat/gpiozero/led_button.py`](compat/gpiozero/) | **gpiozero** LED/Button via the espbridge pin factory |
| [`compat/adafruit/bme280.py`](compat/adafruit/) | **Adafruit CircuitPython** drivers via the blinka shim |
| [`compat/luma/oled.py`](compat/luma/) | **luma.oled** display library |
| [`compat/smbus/read_register.py`](compat/smbus/) | **smbus2**-style I2C register access |
| [`compat/rpi_gpio/blink.py`](compat/rpi_gpio/) | **RPi.GPIO**-style scripts, only the import changes |

## Staying connected — stability tips

- **Long-running scripts: use the managed link.** `espbridge.connect(keepalive=5)`
  pings the board every 5 s and transparently reconnects after an unplug,
  reset, or brown-out — your code keeps using the same object
  (`shared_connection.py` shows the pattern).
- **Address boards by name, not port.** USB COM numbers hop; a stored name
  doesn't. Name each board once (`espbridge -p COM7 set-name sensors`), then
  `Bridge(name="sensors")` finds it on any port — or over Bluetooth with
  `Bridge(ble="sensors")`.
- **Re-attach without rebooting.** Opening the serial port resets the board by
  default; pass `Bridge(reset_on_open=False)` to reconnect to the running
  firmware with all peripheral state intact.
- **One BLE session, kept open**, beats connect/disconnect cycles — the OS can
  hold a closing Bluetooth connection for a few seconds, which makes rapid
  reconnects flaky.
- **Don't bring Wi-Fi up over a Bluetooth session on the classic ESP32** — the
  firmware refuses with `NO_MEM` rather than letting the radio starve the BLE
  link. Use USB (or an S3) for workflows that need both at once.
