Atom Yang
Published

A Tiny ESPHome Desktop Gadget Powered By XIAO

Build a desktop gadget with Seeed XIAO ESP32-C3 + ESPHome + HA: lights up, shows editable text, and responds to button automations.

IntermediateFull instructions provided2 hours10

Things used in this project

Hardware components

XIAO ESP32C3
Seeed Studio XIAO ESP32C3
×1
Seeed Studio XIAO Expansion Board
Seeed Studio XIAO Expansion Board
×1
Seeed Studio IoT Button
×1
Seeed Studio Grove - Blue LED
×1

Software apps and online services

Home Assistant
Home Assistant

Story

Read more

Schematics

Roboto-Regular.ttf

Code

Example Code

YAML
ESPHome Builder
esphome:
  name: xiao-esp32-c3
  friendly_name: XIAO-ESP32-C3

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

logger:

api:
  encryption:
    key: "xxx"

ota:
  - platform: esphome
    password: "xxxx"

wifi:
  ssid: xxxx
  password: xxxx
  ap:
    ssid: "Xiao-Esp32-C3 Fallback Hotspot"
    password: "LiMaVGJhvXsP"

captive_portal:

# ---------- I2C: Onboard 0.96" SSD1306 ----------
i2c:
  sda: GPIO6
  scl: GPIO7
  scan: true

# ---------- Font (ensure /config/esphome/fonts/Roboto-Regular.ttf exists) ----------
font:
  - file: "fonts/Roboto-Regular.ttf"
    id: oled_font
    size: 20

# ---------- Text and screen state ----------
globals:
  - id: screen_on
    type: bool
    restore_value: yes
    initial_value: 'true'

text:
  - platform: template
    name: "OLED Text"
    id: oled_text
    mode: text
    optimistic: true
    min_length: 0
    max_length: 32
    restore_value: true
    initial_value: "Hello Atom!"
    on_value:
      - component.update: oled

# ---------- OLED Display ----------
display:
  - platform: ssd1306_i2c
    id: oled
    model: "SSD1306 128x64"
    address: 0x3C
    rotation: 0
    update_interval: 1s
    lambda: |-
      if (!id(screen_on)) {
        it.fill(Color::BLACK);
        return;
      }
      it.fill(Color::BLACK);
      std::string msg = id(oled_text).state;
      if (msg.empty()) msg = "Hello Atom!";
      it.printf(64, 32, id(oled_font), TextAlign::CENTER, "%s", msg.c_str());

# ---------- Grove LED (A0/D0 → GPIO2; to avoid strapping use D10→GPIO10) ----------
switch:
  - platform: gpio
    id: grove_led
    name: "Grove LED"
    pin:
      number: GPIO2          # Currently on A0/D0: D0→GPIO2
      # number: GPIO10       # ← If you move LED to D10, change to GPIO10
      mode:
        output: true
      inverted: false
    restore_mode: RESTORE_DEFAULT_OFF

  - platform: template
    id: oled_power
    name: "OLED Screen"
    restore_mode: RESTORE_DEFAULT_ON   # ← Fix: replaces restore_state
    optimistic: true
    turn_on_action:
      - lambda: |-
          id(screen_on) = true;
      - component.update: oled
    turn_off_action:
      - lambda: |-
          id(screen_on) = false;
      - component.update: oled

  - platform: restart
    name: "XIAO ESP32C3 Restart"

# ---------- Power-on self-test: blink once ----------
interval:
  - interval: 1s
    then:
      - if:
          condition:
            lambda: 'static bool done=false; if(done) return false; done=true; return true;'
          then:
            - switch.turn_on: grove_led
            - delay: 500ms
            - switch.turn_off: grove_led

Credits

Atom Yang
2 projects • 1 follower

Comments