#include <PubSubClient.h>
#include <ArduinoJson.h>
// --- Wi-Fi Credentials ---
const char* ssid = "Afrihost Fibre DJ";
const char* password = "0824435505";
// --- MQTT Configuration ---
const char* mqttServer = "mqtt.eclipseprojects.io";
const int mqttPort = 1883;
const char* clientID = "ESP32-FanController";
const char* topicCurve = "esp/fan12/curve/set";
const char* topicTemp = "esp/fan12/temp";
const char* topicStatus = "esp/fan12/status";
const char* topicData = "esp/fan12/data";
// --- Pin Assignments ---
const int pwmPin = 18;
const int tachPin = 19;
// --- PWM Settings ---
const int pwmFreq = 500;
const int pwmPeriod_us = 1000000 / pwmFreq;
// --- Cooling Curve ---
int fanCurve[6] = { 0, 20, 40, 60, 80, 100 };
float currentTemp = 0.0;
int currentDuty = 0;
int lastIndex = -1;
// --- Tachometer Pulse Count ---
volatile unsigned long tachPulses = 0;
unsigned long lastRPMCalc = 0;
int currentRPM = 0;
// --- MQTT Setup ---
WiFiClient espClient;
PubSubClient client(espClient);
// --- Interrupt Handler ---
void IRAM_ATTR onTachPulse() {
tachPulses++;
}
void connectWiFi() {
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" connected");
}
void mqttCallback(char* topic, byte* payload, unsigned int length) {
String msg;
for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
if (String(topic) == topicCurve) {
StaticJsonDocument<128> doc;
if (!deserializeJson(doc, msg) && doc.containsKey("curve")) {
JsonArray curve = doc["curve"];
for (int i = 0; i < 6 && i < curve.size(); i++) {
fanCurve[i] = curve[i];
}
Serial.println("✔️ Fan curve updated.");
}
}
if (String(topic) == topicTemp) {
currentTemp = msg.toFloat();
}
}
void reconnectMQTT() {
while (!client.connected()) {
Serial.print("Reconnecting MQTT...");
if (client.connect(clientID)) {
client.subscribe(topicCurve);
client.subscribe(topicTemp);
Serial.println(" connected");
} else {
delay(1000);
}
}
}
int getCurveIndex(float temp) {
if (temp <= 30) return 0;
if (temp <= 35) return 1;
if (temp <= 40) return 2;
if (temp <= 45) return 3;
if (temp <= 50) return 4;
return 5;
}
void setup() {
Serial.begin(115200);
pinMode(pwmPin, OUTPUT);
pinMode(tachPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(tachPin), onTachPulse, FALLING);
connectWiFi();
client.setServer(mqttServer, mqttPort);
client.setCallback(mqttCallback);
}
void loop() {
if (!client.connected()) reconnectMQTT();
client.loop();
// --- Duty Calculation ---
int index = getCurveIndex(currentTemp);
int newDuty = fanCurve[index];
if (index != lastIndex) {
lastIndex = index;
Serial.printf("⚠️ Temp range changed → Index: %d → Duty: %d%%\n", index, newDuty);
// TODO: Beep buzzer here if needed
}
currentDuty = newDuty;
// --- PWM Signal ---
int highTime = (currentDuty * pwmPeriod_us) / 100;
int lowTime = pwmPeriod_us - highTime;
digitalWrite(pwmPin, HIGH);
delayMicroseconds(highTime);
digitalWrite(pwmPin, LOW);
delayMicroseconds(lowTime);
// --- Every 1 Second: Publish Status ---
unsigned long now = millis();
if (now - lastRPMCalc > 1000) {
noInterrupts();
unsigned long pulses = tachPulses;
tachPulses = 0;
interrupts();
currentRPM = (pulses / 2) * 60;
lastRPMCalc = now;
String status = (currentDuty > 0) ? "ON" : "OFF";
StaticJsonDocument<128> doc;
doc["rpm"] = currentRPM;
doc["duty"] = currentDuty;
doc["status"] = status;
char buffer[128];
serializeJson(doc, buffer);
client.publish(topicData, buffer);
client.publish(topicStatus, status.c_str());
Serial.printf("📤 Published → RPM: %d | Duty: %d%% | Status: %s\n", currentRPM, currentDuty, status.c_str());
}
}