Esp-tapo: ESPHome custom component to control Tapo devices locally, without Home Assistant

Hello everyone! First of all, I’m not sure whether other similar components already exist, but here is my own.

Previous context

While watching this video, I discovered a very interesting Matter feature called bindings: they basically allow smart home devices to communicate directly with each other without a central hub.

I found this pretty interesting, especially for presence-based automations that control critical lights as a fallback for Home Assistant.

My case

One of my critical lights is a Tapo bulb in the bathroom. The manual switch is quite far from the door and tedious to reach, so if Home Assistant fails, manual control isn’t very comfortable. I use an ESP32-C3 with an LD2410C to turn it on based on presence.

So I thought: why not create a custom component to turn the light on and off directly from the ESP32, as a fallback for Home Assistant? And here it is: esp-tapo.


esp-tapo

It’s a custom component that lets the ESP control Tapo devices over their local API (KLAP v2). It currently supports:

  • Lights (RGB/CCT bulbs like the L530) as an ESPHome light platform: on/off, brightness, RGB color and color temperature (2500–6500 K).
  • Plugs/switches (P100/P110/P115…) as an ESPHome switch platform: on/off.

Example: turn on an L530 using an LD2410C if Home Assistant is down

substitutions:
  device_name: "l530-motion"
  friendly_name: "L530 Motion Sensor"

esphome:
  name: ${device_name}
  friendly_name: ${friendly_name}

esp32:
  board: esp32-c3-devkitm-1
  framework:
    type: esp-idf

logger:

api:
  encryption:
    key: !secret api_key

ota:
  - platform: esphome
    password: !secret ota_password

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: "${device_name} fallback"
    password: !secret ap_password

captive_portal:

# ---------------------------------------------------------------------
# External `tapo` component (this repository)
# ---------------------------------------------------------------------
external_components:
  - source: github://Alejandro12120/esp-tapo
    components: [tapo]

# Tapo hub: ONE hub = ONE device (IP + app credentials).
tapo:
  id: l530_hub
  host: !secret tapo_l530_host
  username: !secret tapo_username
  password: !secret tapo_password
  update_interval: 30s  # poll get_device_info to mirror the real device state

# ---------------------------------------------------------------------
# mmWave LD2410 radar (UART)
# ---------------------------------------------------------------------
uart:
  id: ld2410_uart
  tx_pin: GPIO5
  rx_pin: GPIO4
  baud_rate: 115200
  parity: NONE
  stop_bits: 1

ld2410:
  id: ld2410_radar
  uart_id: ld2410_uart

binary_sensor:
  - platform: ld2410
    ld2410_id: ld2410_radar
    has_target:
      name: "Presence"
      on_press:
        # Fallback: only turn the bulb on if NO API client is connected
        # (e.g. Home Assistant down or unreachable).
        - if:
            condition:
              lambda: 'return !api::global_api_server->is_connected();'
            then:
              - light.turn_on:
                  id: tapo_light
                  brightness: 100%
      on_release:
        # Symmetric off: only when there is no API client either.
        - if:
            condition:
              lambda: 'return !api::global_api_server->is_connected();'
            then:
              - delay: 30s
              - light.turn_off: tapo_light

# ---------------------------------------------------------------------
# Light: Tapo L530 bulb (on/off + brightness + RGB + color temperature)
# ---------------------------------------------------------------------
light:
  - platform: tapo
    id: tapo_light
    name: "L530 Bulb"
    tapo_id: l530_hub
    # Supported modes: RGB (hue/saturation) and COLOR_TEMPERATURE (2500-6500 K).
    # Brightness/color control from HA or automations is translated to
    # set_device_info automatically.
    # internal: true hides the entity from the API client (Home Assistant) but
    # it still works in ESPHome automations/lambdas. Remove this line to also
    # expose it to HA.
    internal: true
    # restore_mode controls the state written on ESP boot.
    # RESTORE_DEFAULT_OFF: turns the bulb off when the ESP powers on.
    # ALWAYS_ON / RESTORE_DEFAULT_ON: tries to keep the bulb on.
    restore_mode: RESTORE_DEFAULT_OFF

Thanks

  • python-kasa — reference implementation of the KLAP v2 protocol (handshake, encryption, session handling).
  • omegahiro/tapo-esp32 — ESP32 implementation (Arduino), tested against real devices, the basis for the crypto port.
  • mihai-dinculescu/tapo — Rust client that documents the KLAP request flow and device commands.
  • TP-Link community — reports on local API behavior across firmware versions and the “Third-Party Compatibility” requirement.
  • ESPHome — the platform this component is built on (docs, external component system, light/switch APIs).