From 5d2897e7e02ff456a9e552d564fe4627b5b6c788 Mon Sep 17 00:00:00 2001 From: dtoro Date: Sun, 29 Mar 2026 22:19:17 +0200 Subject: [PATCH] chore: v1 --- .gitignore | 41 +++++++++++++++ README.md | 120 ++++++++++++++++++++++++++++++++++++++++++ arduino/main.ino | 133 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 294 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 arduino/main.ino diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a101992 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# macOS +.DS_Store +.AppleDouble +.LSOverride +*.swp +*.swo +*~ +.vscode/ + +# Arduino +build/ +.clang-format +*.o +*.a +*.so +*.so.* +*.dylib +*.dll +*.exe + +# Arduino IDE +.pioenvs/ +.piolibdeps/ +.pio/ +.vscode-pio + +# PlatformIO +.platformio/ +platformio.ini + +# Build directories +bin/ +obj/ + +# Editor backups +*.bak +*.tmp + +# Dependencies and libraries +node_modules/ +dist/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..db8e877 --- /dev/null +++ b/README.md @@ -0,0 +1,120 @@ +# ESP32-C3 Fan Controller + +An automatic server fan controller built on the ESP32-C3, featuring temperature-based relay control, OLED status display, and Home Assistant integration via MQTT. + +## Features + +- **Auto fan control** — relay switches ON/OFF based on temperature thresholds with hysteresis +- **DHT22 sensor** — reads temperature and humidity every second +- **SSD1306 OLED** — displays live temp, humidity, and fan state +- **MQTT publishing** — streams sensor data and fan state to Home Assistant +- **WiFi** — connects on boot, auto-reconnects if MQTT drops + +## Hardware + +| Component | Details | +|---|---| +| Microcontroller | ESP32-C3 (with onboard OLED) | +| Sensor | DHT22 (temperature + humidity) | +| Relay | 5V relay module (active-LOW) | +| Fan | USB 5V fan | +| Display | SSD1306 128×64 OLED (I2C) | + +## Wiring + +| ESP32-C3 Pin | Connected To | +|---|---| +| `GPIO2` | DHT22 DATA (+ 4.7kΩ pullup to 3V3) | +| `GPIO3` | Relay IN | +| `GPIO5` (SDA) | OLED SDA | +| `GPIO6` (SCL) | OLED SCL | +| `3V3` | DHT22 VCC, OLED VCC | +| `GND` | DHT22 GND, OLED GND, Relay GND | + +Relay wiring: +- **COM** → USB 5V +- **NO** → Fan + (red wire) +- Fan − (black wire) → GND + +## Configuration + +Copy `secrets.h.example` to `secrets.h` and fill in your values: + +```cpp +#define WIFI_SSID "your_wifi_ssid" +#define WIFI_PASS "your_wifi_password" +#define MQTT_HOST "192.168.1.X" // Home Assistant IP +#define MQTT_USER "mqtt_user" +#define MQTT_PASS "mqtt_pass" +``` + +### Temperature Thresholds + +Edit these defines in `fan_controller.ino`: + +```cpp +#define TEMP_ON 28.0 // Fan turns ON above this (°C) +#define TEMP_OFF 25.0 // Fan turns OFF below this (°C) +``` + +## MQTT Topics + +| Topic | Direction | Payload | +|---|---|---| +| `esp32c3/temperature` | Publish | `23.5` (°C) | +| `esp32c3/humidity` | Publish | `41` (%) | +| `esp32c3/fan/state` | Publish | `ON` / `OFF` | + +## Home Assistant Setup + +Add to `configuration.yaml`: + +```yaml +mqtt: + sensor: + - name: "Room Temperature" + unique_id: "esp32c3_temperature" + state_topic: "esp32c3/temperature" + unit_of_measurement: "°C" + device_class: temperature + + - name: "Room Humidity" + unique_id: "esp32c3_humidity" + state_topic: "esp32c3/humidity" + unit_of_measurement: "%" + device_class: humidity + + binary_sensor: + - name: "Server Fan" + unique_id: "esp32c3_fan" + state_topic: "esp32c3/fan/state" + payload_on: "ON" + payload_off: "OFF" + device_class: running +``` + +Restart HA after saving. Requires the **Mosquitto broker** add-on installed and running. + +## Dependencies + +Install via Arduino IDE Library Manager: + +- [U8g2](https://github.com/olikraus/u8g2) — OLED display +- [DHT sensor library](https://github.com/adafruit/DHT-sensor-library) — DHT22 +- [PubSubClient](https://github.com/knolleary/pubsubclient) — MQTT client +- WiFi (built-in with ESP32 board package) + +## Project Structure + +``` +esp32c3-fan-controller/ +├── fan_controller.ino # Main sketch +├── secrets.h # WiFi & MQTT credentials (gitignored) +├── secrets.h.example # Credentials template +├── .gitignore +└── README.md +``` + +## License + +MIT diff --git a/arduino/main.ino b/arduino/main.ino new file mode 100644 index 0000000..0eed87e --- /dev/null +++ b/arduino/main.ino @@ -0,0 +1,133 @@ +#include +#include +#include +#include + +// --- WiFi --- +#define WIFI_SSID "GL-AXT1800-714" +#define WIFI_PASS "BDX924MWFW" + +// --- MQTT --- +#define MQTT_HOST "192.168.8.101" // your HA IP +#define MQTT_PORT 1883 +#define MQTT_USER "mqtt_user" // Mosquitto user +#define MQTT_PASS "Naughty0pArrot" +#define MQTT_CLIENT "esp32c3_fan" + +// Topics +#define TOPIC_TEMP "esp32c3_fan/temperature" +#define TOPIC_HUMIDITY "esp32c3_fan/humidity" +#define TOPIC_FAN_STATE "esp32c3_fan/fan/state" +#define TOPIC_FAN_CMD "esp32c3_fan/fan/set" // HA sends ON/OFF here + +// --- Pins --- +#define DHTPIN 2 +#define DHTTYPE DHT22 +#define RELAY_PIN 3 + +// --- Thresholds --- +#define TEMP_ON 28.0 +#define TEMP_OFF 25.0 + +// --- OLED --- +#define OLED_RESET U8X8_PIN_NONE +#define OLED_SDA 5 +#define OLED_SCL 6 + +U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, OLED_RESET, OLED_SCL, OLED_SDA); +DHT dht(DHTPIN, DHTTYPE); +WiFiClient wifiClient; +PubSubClient mqtt(wifiClient); + +int xOffset = 30; +int yOffset = 30; +bool fanState = false; + +void setFan(bool on) { + fanState = on; + digitalWrite(RELAY_PIN, on ? LOW : HIGH); // active-LOW relay + mqtt.publish(TOPIC_FAN_STATE, on ? "ON" : "OFF", true); +} + +void connectWiFi() { + Serial.printf("Connecting to %s", WIFI_SSID); + WiFi.begin(WIFI_SSID, WIFI_PASS); + while (WiFi.status() != WL_CONNECTED) { + delay(500); Serial.print("."); + } + Serial.printf("\nWiFi connected: %s\n", WiFi.localIP().toString().c_str()); +} + +void connectMQTT() { + while (!mqtt.connected()) { + Serial.print("Connecting MQTT..."); + if (mqtt.connect(MQTT_CLIENT, MQTT_USER, MQTT_PASS)) { + Serial.println("connected!"); + } else { + Serial.printf("failed rc=%d, retry in 5s\n", mqtt.state()); + delay(5000); + } + } +} + +void handle_oled(float tempC, float humidity) { + char line1[20], line2[20], line3[20]; + snprintf(line1, sizeof(line1), "T: %.1f C", tempC); + snprintf(line2, sizeof(line2), "H: %.0f %%", humidity); + snprintf(line3, sizeof(line3), "Fan: %s", fanState ? "ON" : "OFF"); + + u8g2.clearDisplay(); + u8g2.clearBuffer(); + u8g2.setFont(u8g2_font_4x6_tr); + u8g2.drawStr(xOffset, yOffset, line1); + u8g2.drawStr(xOffset, yOffset + 10, line2); + u8g2.drawStr(xOffset, yOffset + 20, line3); + u8g2.sendBuffer(); +} + +void setup() { + Serial.begin(115200); + dht.begin(); + + pinMode(RELAY_PIN, OUTPUT); + setFan(false); // fan OFF at boot + + u8g2.begin(); + u8g2.setContrast(255); + u8g2.setBusClock(400000); + u8g2.clearDisplay(); + + connectWiFi(); + mqtt.setServer(MQTT_HOST, MQTT_PORT); + connectMQTT(); + + Serial.println("Ready."); +} + +void loop() { + if (!mqtt.connected()) connectMQTT(); + mqtt.loop(); + + float tempC = dht.readTemperature(); + float humidity = dht.readHumidity(); + + if (isnan(tempC) || isnan(humidity)) { + Serial.println("DHT22 read failed"); + delay(2000); + return; + } + + // Auto fan control with hysteresis + if (!fanState && tempC >= TEMP_ON) setFan(true); + if (fanState && tempC <= TEMP_OFF) setFan(false); + + // Publish to MQTT + char buf[10]; + dtostrf(tempC, 4, 1, buf); mqtt.publish(TOPIC_TEMP, buf); + dtostrf(humidity, 4, 0, buf); mqtt.publish(TOPIC_HUMIDITY, buf); + + Serial.printf("T:%.1fC H:%.0f%% Fan:%s\n", tempC, humidity, fanState ? "ON" : "OFF"); + handle_oled(tempC, humidity); + + delay(3000); +} \ No newline at end of file