feat: integrate DHT22, OLED, WiFi/MQTT with 5-level temp-driven PWM fan
Fan speed is now auto-selected from a temperature table (levels 1-5 at 28/30/32/34/36C mapping to PWM duties 230/236/242/249/255) with 1C hysteresis. State and level are published to MQTT for Home Assistant. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
183
main/main.ino
Normal file
183
main/main.ino
Normal file
@@ -0,0 +1,183 @@
|
||||
// fan4life main
|
||||
// ESP32-C3 | Arduino core 3.x
|
||||
// 2-pin 5V fan via IRLZ44N MOSFET (PWM)
|
||||
// DHT22 temp/humidity, SSD1306 OLED, WiFi + MQTT (state publish only)
|
||||
|
||||
#include <U8g2lib.h>
|
||||
#include <DHT.h>
|
||||
#include <WiFi.h>
|
||||
#include <PubSubClient.h>
|
||||
#include "secrets.h"
|
||||
|
||||
// --- MQTT topics (publish only) ---
|
||||
#define TOPIC_TEMP "esp32c3_fan/temperature"
|
||||
#define TOPIC_HUMIDITY "esp32c3_fan/humidity"
|
||||
#define TOPIC_FAN_STATE "esp32c3_fan/fan/state"
|
||||
#define TOPIC_FAN_SPEED "esp32c3_fan/fan/speed"
|
||||
|
||||
// --- Pins ---
|
||||
#define DHTPIN 2
|
||||
#define DHTTYPE DHT22
|
||||
#define FAN_PIN 3
|
||||
#define OLED_SDA 5
|
||||
#define OLED_SCL 6
|
||||
|
||||
// --- PWM ---
|
||||
#define PWM_FREQ 20000
|
||||
#define PWM_RES 8
|
||||
|
||||
// --- Temperature -> fan level table ---
|
||||
// Level 0 = off. Higher temp -> higher level. Hysteresis: drop a level
|
||||
// only after temp falls TEMP_HYST below the level's threshold.
|
||||
#define TEMP_HYST 1.0
|
||||
struct FanLevel { const char* label; float tempOn; uint8_t duty; };
|
||||
const FanLevel LEVELS[] = {
|
||||
{ "1", 28.0, 230 },
|
||||
{ "2", 30.0, 236 },
|
||||
{ "3", 32.0, 242 },
|
||||
{ "4", 34.0, 249 },
|
||||
{ "5", 36.0, 255 },
|
||||
};
|
||||
const uint8_t NUM_LEVELS = sizeof(LEVELS) / sizeof(LEVELS[0]);
|
||||
#define TEMP_OFF (LEVELS[0].tempOn - TEMP_HYST)
|
||||
|
||||
// --- OLED ---
|
||||
#define OLED_RESET U8X8_PIN_NONE
|
||||
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;
|
||||
|
||||
uint8_t fanLevel = 0; // 0 = off, 1..NUM_LEVELS
|
||||
|
||||
void applyFan() {
|
||||
if (fanLevel == 0) {
|
||||
ledcWrite(FAN_PIN, 0);
|
||||
return;
|
||||
}
|
||||
uint8_t duty = LEVELS[fanLevel - 1].duty;
|
||||
if (duty > 0 && duty < 200) {
|
||||
ledcWrite(FAN_PIN, 255); // kick-start
|
||||
delay(300);
|
||||
}
|
||||
ledcWrite(FAN_PIN, duty);
|
||||
}
|
||||
|
||||
void publishFan() {
|
||||
mqtt.publish(TOPIC_FAN_STATE, fanLevel > 0 ? "ON" : "OFF", true);
|
||||
const char* label = fanLevel > 0 ? LEVELS[fanLevel - 1].label : "0";
|
||||
mqtt.publish(TOPIC_FAN_SPEED, label, true);
|
||||
}
|
||||
|
||||
void setFanLevel(uint8_t level) {
|
||||
if (level == fanLevel) return;
|
||||
fanLevel = level;
|
||||
applyFan();
|
||||
publishFan();
|
||||
}
|
||||
|
||||
// Pick the level for a given temperature, with hysteresis from current level.
|
||||
uint8_t levelForTemp(float tempC) {
|
||||
uint8_t target = 0;
|
||||
for (uint8_t i = 0; i < NUM_LEVELS; i++) {
|
||||
if (tempC >= LEVELS[i].tempOn) target = i + 1;
|
||||
}
|
||||
// Hysteresis: only drop if temp is clearly below the current level's on-point.
|
||||
if (target < fanLevel) {
|
||||
float dropBelow = LEVELS[fanLevel - 1].tempOn - TEMP_HYST;
|
||||
if (tempC > dropBelow) target = fanLevel;
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
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!");
|
||||
publishFan();
|
||||
} 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);
|
||||
const char* lvl = fanLevel > 0 ? LEVELS[fanLevel - 1].label : "0";
|
||||
snprintf(line3, sizeof(line3), "Fan: %s L%s",
|
||||
fanLevel > 0 ? "ON" : "OFF", lvl);
|
||||
|
||||
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);
|
||||
delay(500);
|
||||
|
||||
ledcAttach(FAN_PIN, PWM_FREQ, PWM_RES);
|
||||
ledcWrite(FAN_PIN, 0);
|
||||
|
||||
dht.begin();
|
||||
|
||||
u8g2.begin();
|
||||
u8g2.setContrast(255);
|
||||
u8g2.setBusClock(400000);
|
||||
u8g2.clearDisplay();
|
||||
|
||||
connectWiFi();
|
||||
mqtt.setServer(MQTT_HOST, MQTT_PORT);
|
||||
connectMQTT();
|
||||
|
||||
Serial.println("Ready.");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (WiFi.status() != WL_CONNECTED) connectWiFi();
|
||||
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: temperature -> level (with hysteresis)
|
||||
setFanLevel(levelForTemp(tempC));
|
||||
|
||||
// Publish telemetry
|
||||
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 L%u\n",
|
||||
tempC, humidity, fanLevel > 0 ? "ON" : "OFF", fanLevel);
|
||||
handle_oled(tempC, humidity);
|
||||
|
||||
delay(3000);
|
||||
}
|
||||
Reference in New Issue
Block a user