Skip to content

OGD09/cplug

Repository files navigation

cplug

CI

ESP32 Wi-Fi smart plug: relay control over a web interface, momentary physical button, 16x2 I2C LCD status display, and captive-portal Wi-Fi provisioning. Built on ESP-IDF (no Arduino dependency).

This is a feature-identical port of the original Arduino sketch, kept for reference in assets/cplug.ino.

cplug

Features

  • Relay toggle over HTTP (single-page web UI, XHR, no reload).
  • Momentary physical button: relay ON while held, OFF on release; web toggling is ignored while the button is held.
  • Captive portal provisioning: on first boot (or when the stored network is unreachable) the device opens the WPA2 access point CPLUG1; any DNS lookup resolves to the device and unknown URLs redirect to the configuration page, so the portal opens automatically on phones and laptops.
  • Credentials persisted in NVS; they survive reflashing.
  • 16x2 LCD: shows the management IP when reachable, otherwise the provisioning AP credentials.
  • Fixed station MAC address and hostname (cplug1).
  • The device keeps retrying the stored network in the background even while the provisioning AP is active.

Hardware

Function GPIO
Relay output 15
Push button 4 (active low, internal pull-up)
LCD I2C SDA 21
LCD I2C SCL 22

LCD: HD44780 16x2 with PCF8574 I2C backpack at address 0x27. Target: ESP32 (WROOM-32 class module, 4 MB flash).

All pins, network identity, and AP credentials are defined in main/app_config.h.

Wiring diagram: img/cplug_diagram_clear.png. A 3D-printable case is available in assets/ (cplug_case.stl, cplug_lid.stl).

HTTP API

Route Method Effect
/ GET Control page (HTML)
/toggle GET Invert relay (unless button held); returns 1/0
/config GET Wi-Fi configuration page (HTML)
/save POST Store ssid/password in NVS, then reboot
any other * 302 redirect to /config (captive portal)

Provisioning flow

  1. On boot, stored credentials are read from NVS (namespace wifi).
  2. If present, the device tries to join the network for 10 seconds.
  3. On failure (or missing credentials) it starts the access point CPLUG1 (see app_config.h for the password) at 192.168.4.1, with a catch-all DNS server: connecting to the AP pops up the configuration page automatically (captive portal).
  4. Submit SSID and password; the device reboots and joins the configured network. The LCD then shows the management IP.

Build and flash

Requires ESP-IDF v5.2 or later (uses the i2c_master driver API).

. $IDF_PATH/export.sh   # or: get_idf, depending on your setup
idf.py set-target esp32
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor

Project layout

main/                     application (entry point, Wi-Fi, HTTP, relay)
components/lcd1602_i2c/   HD44780/PCF8574 LCD driver component
components/dns_server/    captive-portal DNS server component
tests/host/               host-side unit tests (no hardware required)
assets/cplug.ino          original Arduino sketch (the port's reference)
assets/esp32s_relay.ino   divergent sketch variant (adds /hold and /5sec
                          momentary routes, no captive portal) kept in
                          case those features are wanted later
assets/*.stl              3D-printable case
img/                      photos and wiring diagram
sdkconfig.defaults        build defaults (4 MB flash, large app partition)
.github/workflows/        CI (lint, host tests, firmware build)

Development

CI runs three checks on every push and pull request; run them locally with:

# Code style (clang-format 22, pinned in CI; config in .clang-format)
find main components tests -name '*.c' -o -name '*.h' \
  | xargs clang-format --dry-run --Werror

# Static analysis
cppcheck --enable=warning,style,performance,portability \
  --error-exitcode=1 --inline-suppr --check-level=exhaustive \
  --suppress=missingIncludeSystem \
  -I main -I components/lcd1602_i2c/include \
  -I components/dns_server/include main components tests

# Host unit tests
gcc -Wall -Wextra -Werror -I main \
  -o test_url_decode main/url_decode.c tests/host/test_url_decode.c
./test_url_decode

Documentation-only changes (*.md, assets/, img/, LICENSE) skip CI.

Differences from the original sketch

Two deliberate deviations, both improvements; everything else is feature-identical:

  • The stored Wi-Fi password is never echoed to the serial console (the sketch printed it after saving).
  • The captive-portal DNS server only runs while the provisioning AP is active (the sketch also bound UDP port 53 when connected as a station, answering with a useless 0.0.0.0).

Security notes

  • The web interface is unauthenticated HTTP, intended for a trusted LAN.
  • The provisioning AP password is compiled into the firmware and shown on the LCD while the AP is active.

License

MIT

About

ESP32 Wi-Fi smart plug: web-controlled relay, LCD status display, captive-portal provisioning. Pure ESP-IDF.

Topics

Resources

License

Stars

1 star

Watchers

1 watching

Forks

Contributors