{"id":50622371,"url":"https://github.com/merlindorin/esp32-fan-controller","last_synced_at":"2026-06-06T13:02:20.608Z","repository":{"id":360931914,"uuid":"1251841393","full_name":"merlindorin/esp32-fan-controller","owner":"merlindorin","description":"A small KiCad board to keep my home server cabinet cool. 4 PWM fans, 4 temperature probes, one ESP32.","archived":false,"fork":false,"pushed_at":"2026-05-28T12:37:50.000Z","size":20880,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-28T14:13:41.201Z","etag":null,"topics":["1-wire","ds18b20","electronics","esp32","esphome","fan-controller","hardware","home-assistant","home-automation","homelab","kicad","noctua","pwm-fan","temperature-sensor"],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/merlindorin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-28T00:48:55.000Z","updated_at":"2026-05-28T13:49:43.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/merlindorin/esp32-fan-controller","commit_stats":null,"previous_names":["merlindorin/esp32-fan-controller"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/merlindorin/esp32-fan-controller","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/merlindorin%2Fesp32-fan-controller","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/merlindorin%2Fesp32-fan-controller/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/merlindorin%2Fesp32-fan-controller/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/merlindorin%2Fesp32-fan-controller/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/merlindorin","download_url":"https://codeload.github.com/merlindorin/esp32-fan-controller/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/merlindorin%2Fesp32-fan-controller/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33983046,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-06T02:00:07.033Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["1-wire","ds18b20","electronics","esp32","esphome","fan-controller","hardware","home-assistant","home-automation","homelab","kicad","noctua","pwm-fan","temperature-sensor"],"created_at":"2026-06-06T13:02:14.191Z","updated_at":"2026-06-06T13:02:20.601Z","avatar_url":"https://github.com/merlindorin.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# ESP32 Fan Controller\n\n**A small KiCad board to keep my home server cabinet cool. 4 PWM fans, 4 temperature probes, one ESP32.**\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![Status: PCB ordered](https://img.shields.io/badge/status-PCB%20%26%20parts%20ordered-blue.svg)](#roadmap)\n[![KiCad: 10](https://img.shields.io/badge/KiCad-10-blueviolet.svg)](https://www.kicad.org/)\n[![Schematic: ERC clean](https://img.shields.io/badge/schematic-ERC%20clean-2ea44f.svg)](#)\n[![PCB: DRC clean](https://img.shields.io/badge/PCB-DRC%20clean-2ea44f.svg)](#)\n\n\u003ca href=\"docs/images/pcb-3d-render.png\"\u003e\u003cimg src=\"docs/images/pcb-3d-render.png\" alt=\"ESP32 Fan Controller, KiCad 3D render\" width=\"60%\"\u003e\u003c/a\u003e\n\n\u003c/div\u003e\n\n## Why this exists\n\nMy home servers live in a small cabinet, hidden inside a piece of furniture I built around them. It is a credenza with a stone top, a perforated metal grille set into that top so the warm air can escape upward, and cane mesh side panels so fresh air can come in from below.\n\nThe room itself has air conditioning, but the cabinet does not. Without something actively pushing the warm air out, it just stacks up under the stone. So I needed a way to manage the airflow.\n\n\u003ctable align=\"center\"\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\n      \u003ca href=\"docs/images/server-cabinet.jpg\"\u003e\u003cimg src=\"docs/images/server-cabinet.jpg\" alt=\"The server cabinet, with the perforated stone-top grille and cane-mesh panels, click for full size\" width=\"420\"\u003e\u003c/a\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003csub\u003eThe cabinet. Stone top with the perforated exhaust grille against the wall, servers behind the cane mesh panels.\u003c/sub\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\nI started with two breadboard prototypes (see [How I got here](#how-i-got-here)). The first one was just about understanding the temperatures: where to put the probes, where it actually gets hot. The second one was for the fans, to see how they really move the air through that perforated stone grille.\n\nOnce both questions were answered, I designed this PCB. It is a small two layer board, all through-hole so I can solder it by hand on a Sunday afternoon, around €14 in parts. It runs [ESPHome](https://esphome.io/), which means it shows up directly in Home Assistant without writing any firmware.\n\n\u003e Heads up: the PCB and the parts are ordered, but nothing is assembled yet. The ESPHome config will follow once the board is here.\n\n## Contents\n\n- [Features](#features)\n- [Hardware overview](#hardware-overview)\n  - [ESP32 GPIO map](#esp32-gpio-map)\n- [How I got here](#how-i-got-here)\n- [Build one yourself](#build-one-yourself)\n- [Repository layout](#repository-layout)\n- [Roadmap](#roadmap)\n- [License](#license)\n- [Thanks](#thanks)\n\n## Features\n\n- 4 independent PWM fan channels (designed around Noctua NF-A14 PWM, but any standard 4-pin PC fan works)\n- 4 tachometer (RPM) inputs, open-collector, with on-board pull-ups to 3.3 V\n- 4 DS18B20 1-Wire temperature probes on a shared bus (single 4.7 kΩ pull-up)\n- Single 12 V external input. An on-board MP1584EN buck module generates the 5 V for the ESP32\n- Per-fan PTC fuse and a TVS diode at the 12 V input, for protection\n- I²C expansion header (3.3 V / GND / SDA / SCL)\n- Two indicator LEDs: a green one for power (lit when the 5 V rail is up) and a yellow one for status (driven by GPIO 2)\n- A small tactile button for boot / user input, shared with GPIO 0\n- Everything through-hole, so it can be hand-soldered without a reflow oven\n- Bare board, 155 × 85 mm, with M3 mounting holes\n\n## Hardware overview\n\n| Block      | Component                                                       |\n|------------|-----------------------------------------------------------------|\n| MCU        | ESP32 DevKitC V4 (AZDelivery, 100 % Espressif layout)           |\n| Power      | External 12 V / 2 A PSU, then on-board MP1584EN buck to 5 V     |\n| Fans       | 4 Noctua NF-A14 PWM, plugged into 4-pin headers                 |\n| Sensors    | 4 DS18B20 waterproof probes on screw terminals                  |\n| Protection | 1.5KE15A TVS on the 12 V input, 4 MF-R050 PTC fuses on the fans |\n| Enclosure  | No enclosure for v1, just M3 mounting holes on the bare board   |\n\nPCB is two-layer, 155 × 85 mm, 1.6 mm thick.\n\n\u003ctable align=\"center\"\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\" width=\"50%\"\u003e\n      \u003ca href=\"docs/images/pcb-layout.png\"\u003e\u003cimg src=\"docs/images/pcb-layout.png\" alt=\"PCB top-down layout, click for full size\" width=\"300\"\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"center\" width=\"50%\"\u003e\n      \u003ca href=\"docs/images/pcb-3d-render.png\"\u003e\u003cimg src=\"docs/images/pcb-3d-render.png\" alt=\"KiCad 3D render, click for full size\" width=\"300\"\u003e\u003c/a\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003csub\u003e2D layout. F.Cu in red, B.Cu in blue.\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003csub\u003eKiCad 3D render of the assembled board.\u003c/sub\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n### ESP32 GPIO map\n\n| Pin            | Use                                  |\n|----------------|--------------------------------------|\n| 25, 26, 32, 33 | PWM out for fans 1 to 4              |\n| 34, 35, 36, 39 | Tach in for fans 1 to 4 (input-only) |\n| 23             | 1-Wire bus for the DS18B20 probes    |\n| 21 / 22        | I²C SDA / SCL                        |\n| 2              | Status LED (D3 yellow, R6 470 Ω)     |\n| 0              | Boot / user button (SW1)             |\n\n## How I got here\n\nBefore committing to a PCB, I validated the design with two breadboard stages.\n\n### Stage 1, temperature monitoring with Home Assistant\n\nI started with an ESP32 **LoLin** board and **two DS18B20 probes**, flashed with [ESPHome](https://esphome.io/) and added to Home Assistant. The question for this stage was simple: what does the temperature actually do in the server cabinet? It gave me the baseline graphs that justified active cooling, and it told me where the probes should sit (intake, exhaust, ambient).\n\nThe ESPHome YAML I used is kept here for reference: [`docs/stage1-lolin32-esphome.yaml`](docs/stage1-lolin32-esphome.yaml). Shared 1-Wire bus on GPIO 4, two `dallas_temp` entries by 64-bit address.\n\n\u003ctable align=\"center\"\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\" width=\"50%\"\u003e\n      \u003ca href=\"docs/images/proto-lolint-esp.jpg\"\u003e\u003cimg src=\"docs/images/proto-lolint-esp.jpg\" alt=\"ESP32 LoLin with 2 DS18B20 probes on a protoboard, click for full size\" width=\"300\"\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd align=\"center\" width=\"50%\"\u003e\n      \u003ca href=\"docs/images/homeassistant-esphome.png\"\u003e\u003cimg src=\"docs/images/homeassistant-esphome.png\" alt=\"Home Assistant device page for the LoLin32, click for full size\" width=\"300\"\u003e\u003c/a\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003csub\u003eESP32 LoLin with two DS18B20 probes.\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003csub\u003eHome Assistant device page, both probes live (28.7 °C and 29.8 °C).\u003c/sub\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n### Stage 2, the fan bench\n\nFor the second stage, I drove a fan directly from a separate breadboard, to actually measure how the airflow influences the temperature. Fan position, speed versus Δtemperature, how fast the cabinet responds. This is where I confirmed that the Noctua NF-A14 was the right fan, validated the PWM strategy, and figured out the per-fan power budget.\n\n\u003ctable align=\"center\"\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\n      \u003ca href=\"docs/images/proto-fan-bench.jpg\"\u003e\u003cimg src=\"docs/images/proto-fan-bench.jpg\" alt=\"Fan power and airflow test on a 30 by 70 mm protoboard, click for full size\" width=\"400\"\u003e\u003c/a\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003csub\u003eFan power and airflow test on a 30 × 70 mm protoboard.\u003c/sub\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\nThe PCB in this repository is the consolidation of both stages: 4 probes (intake, exhaust, two ambient) and 4 PWM-controlled fans on a single board instead of breadboards.\n\n## Build one yourself\n\n1. **Order the bare PCB.** Any fab works, I used [Aisler](https://aisler.net/). You can upload `kicad/esp32-fan-controller.kicad_pro` directly, or export gerbers first if your fab needs them.\n2. **Order the parts**, around €14 excluding VAT and shipping. The quick-paste block in [`bom/ORDER.md`](bom/ORDER.md) is verified against live Farnell France stock. Paste it into Farnell's Quick Paste / Multi-Add tool. The full BOM with notes is in [`BOM.md`](BOM.md).\n3. **Pre-set the MP1584EN buck module to 5.00 V before soldering it in.** Apply 12 V on its input with a bench supply, and turn the trimpot until the output reads 5.00 V.\n4. **Hand-solder.** All through-hole. The order I would suggest: passives, then diodes and LEDs, then sockets and headers, then screw terminals, and finally the ESP32 socket.\n5. **Power-up check.** 12 V on J1, confirm 5 V at the M1 output pin, then plug in the ESP32.\n6. **Flash [ESPHome](https://esphome.io/)**, no custom firmware needed. The v1 config will land in `docs/` once the board is assembled. In the meantime, the Stage 1 prototype config at [`docs/stage1-lolin32-esphome.yaml`](docs/stage1-lolin32-esphome.yaml) is a good starting point.\n\n## Repository layout\n\n```\n.\n├── kicad/                            # KiCad 10 project (schematic and PCB)\n├── bom/                              # BOM CSV and ordering sheets\n│   └── orders/                       # Generated distributor cart CSVs\n├── docs/\n│   ├── images/                       # Photos, schematic renders, screenshots\n│   └── stage1-lolin32-esphome.yaml   # ESPHome config used on the temperature prototype\n├── BOM.md                            # Human-readable BOM with notes\n├── LICENSE                           # MIT\n└── README.md\n```\n\n## Roadmap\n\n**Next**\n\n- [ ] Assemble the v1 board and bring it up\n- [ ] Write the [ESPHome](https://esphome.io/) config for the board (4 PWM fans with tach feedback, shared 1-Wire DS18B20 bus) and add it to Home Assistant, same approach as Stage 1\n\n**Later**\n\n- [ ] An actual enclosure for the PCB. The CamdenBoss CNMB/9 DIN-rail kit is a candidate, the board is already short-edge-connector friendly\n- [ ] A v2 silkscreen polish, based on what I learn during assembly\n\n**Done**\n\n- ✅ Schematic, ERC clean\n- ✅ PCB layout, DRC clean, 0 violations, 0 unconnected\n- ✅ Bare PCB ordered (Aisler)\n- ✅ BOM verified against live Farnell stock\n- ✅ Components ordered (Farnell)\n\n## License\n\nMIT, see [LICENSE](LICENSE). Hardware design files are released under the same terms, so feel free to fork, modify, fabricate and share.\n\n## Thanks\n\n- The KiCad team for the EDA suite\n- Espressif for the ESP32 and the DevKitC reference design\n- AZDelivery for an affordable ESP32 DevKitC V4 clone\n- [ESPHome](https://esphome.io/), which turned the Stage 1 prototype into a one evening job\n- [openotters](https://github.com/openotters/openotters), which I used as a template for this README\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmerlindorin%2Fesp32-fan-controller","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmerlindorin%2Fesp32-fan-controller","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmerlindorin%2Fesp32-fan-controller/lists"}