ESP32-S3 PID fan controller with ESPDash web dashboard and BLE Wi-Fi provisioning, built for the Vishes seedling cabinet.
| Component | Part |
|---|---|
| MCU | Seeed XIAO ESP32-S3 |
| Fan controller | Adafruit EMC2101 (I2C 0x4C, PWM mode) |
| Temp probe | DS18B20 one-wire sensor |
| Fan type | 4-wire PC fan (25 kHz PWM) |
| Define | Value | Description |
|---|---|---|
| TEMP_PROBE_PIN | 3 | GPIO pin for DS18B20 one-wire data line (D2 on XIAO ESP32-S3) |
| REPROV_BTN_PIN | 4 | GPIO pin for re-provisioning jumper (D3 on XIAO ESP32-S3 — wire to GND, hold on reset to re-provision) |
| STATUS_LED_PIN | LED_BUILTIN | GPIO pin for the on-board status LED |
| Define | Value (ms) | Description |
|---|---|---|
| CONTROL_INTERVAL_MS | 2000 | Milliseconds between each temperature-read and PID update cycle |
| STATUS_LED_BLINK_MS | 500 | Milliseconds per blink half-period when WiFi is connected |
| Setting | Value |
|---|---|
| Service name | PROV_YourDeviceName |
| PIN | 00000000 |
| App | ESP BLE Provisioning by Espressif (iOS / Android) |
To provision or re-provision:
- Wire D3 (GPIO 4) to GND and hold during reset.
- Open the ESP BLE Provisioning app and scan for
PROV_YourDeviceName. - Enter PIN
00000000, choose your network, and confirm. - Remove the jumper — the board reboots and connects automatically.
Served at http://<device-ip>/dash.
Open http://<device-ip>/ in Safari and use Add to Home Screen for a standalone iOS web clip.
| Widget | Type | Description |
|---|---|---|
| Temperature | Display | Live probe reading in °F |
| Fan % | Display | Current PID output (0–100%) |
| RPM | Display | Tachometer reading from EMC2101 |
| Probe Status | Status | Connected / Disconnected indicator |
| Target Temp | Slider | Setpoint 60–90 °F (persisted to NVS) |
| Kp / Ki / Kd | Sliders | PID gains (persisted to NVS) |
| Run Full On Error | Toggle | Fan at 100% or 0% if probe is lost |
| Parameter | Default | Notes |
|---|---|---|
| Target temp | 75 °F | Persisted to NVS |
| Kp | 2.0 | Proportional gain |
| Ki | 0.1 | Integral gain |
| Kd | 0.0 | Derivative gain |
| Direction | REVERSE | Fan output increases when temp > setpoint |
| Sample time | 2000 ms | Matches CONTROL_INTERVAL_MS in config.h |
| Module | Description |
|---|---|
main.cpp |
Application entry point: setup and main control loop for the seedling fan controller. |
config.h |
Compile-time hardware pin assignments, timing constants, and BLE provisioning settings. |
globals.h |
Extern declarations for shared mutable state defined in globals.cpp. |
globals.cpp |
Definitions and default values for all shared global variables. |
prefs.h |
Interface for loading and saving persistent user settings via NVS. |
fan.h |
Interface for initializing and controlling the EMC2101 PWM fan driver. |
temperature.h |
Interface for the DS18B20 one-wire temperature probe. |
pid_control.h |
Interface for the fan PID controller built on top of the Arduino PID library. |
status_led.h |
Interface for the on-board status LED used to indicate WiFi state. |
dashboard.h |
Interface for the ESPDash web dashboard and its public card widgets. |
provisioning.h |
Interface for BLE-based WiFi provisioning via the Espressif WiFiProv library. |
-
Clone the repo
git clone https://github.com/your-username/vishes-fan-controller.git cd vishes-fan-controller -
Create your local config
cp src/config_local.h.example src/config_local.h
Edit
src/config_local.hand set your ownPROV_SERVICE_NAMEandPROV_POP. -
Open in VS Code with the PlatformIO IDE extension installed.
-
Build and flash using the tasks below.
Open this project in VS Code with the PlatformIO extension, then use Terminal → Run Task:
| Task | Action |
|---|---|
| PIO: Build | Compile firmware |
| PIO: Upload | Flash to connected board |
| PIO: Monitor | Open serial monitor at 115 200 baud |
| PIO: Upload + Monitor | Flash then open serial monitor |
API docs are generated with Doxygen. Run the Docs: Generate + Open VS Code task, or from a terminal:
python scripts/gen_readme.py # regenerate this README
doxygen Doxyfile # build HTML docs (output: docs/html/index.html)This README is consumed by Doxygen as the main page (USE_MDFILE_AS_MAINPAGE = README.md).