Personal hardware project

ESP32 BLE servo-control stack (firmware + Python client)

A full firmware-to-client control link: a custom BLE GATT service on an ESP32 driving two servos, and an asyncio Python client that reconnects on its own.

  • Embedded
  • Hardware
  • Python

01 Problem

I wanted a reliable wireless control link for two servos that a program, not a human, could drive, after Bluetooth Classic proved unstable on Windows.

02 Approach

Wrote Arduino C++ firmware for an ESP32 driving two SG90 servos through a PCA9685 PWM driver, with a TTP223 capacitive touch input.

I replaced the generic Nordic UART profile with a custom BLE GATT service exposing three characteristics — command, response, touch — so command replies never mix with asynchronous gesture events, and classified gestures (tap, double, long-press with duration) in a non-blocking state machine.

Because serial debug output was not available, I built an LED blink-code diagnostic system, which is how I traced idle disconnects to BLE reason code 0x13.

On the host side I wrote a Python bleak client using asyncio, subscribing to the response and touch streams separately, with a link-supervisor task that reconnects using exponential backoff and jitter and re-arms subscriptions each time. A WiFi access-point mode serves as a fallback control path.

03 Stack

  • ESP32
  • PCA9685
  • SG90 servos
  • TTP223
  • Arduino C++
  • BLE GATT
  • Python
  • bleak
  • asyncio

04 Result

A full firmware-to-client control stack, verified against real hardware at each layer, with a documented BLE contract handed off for reuse.

05 What broke, and what fixed it

esp32-ble-servo/diagnostics 3 entries
  • BLE reason code 0x13

    Idle disconnects traced with an LED blink-code diagnostic system, built because serial debug was unavailable.

  • Custom GATT service

    Three characteristics so command replies never interleave with asynchronous gesture events.

  • Self-healing link

    Supervisor task reconnects with exponential backoff and jitter, re-arming subscriptions each time.