// ─────────────────────────────────────────────
// LIBRARIES (these add features to your ESP32)
// ─────────────────────────────────────────────
#include <WiFi.h> // allows ESP32 to connect to WiFi
#include <PubSubClient.h> // allows MQTT communication
#include <HTTPClient.h> // allows API calls over HTTP
#include <ArduinoJson.h> // allows parsing JSON data
#include <DHT.h> // allows reading DHT22 sensor
// ─────────────────────────────────────────────
// WIFI SETTINGS
// ─────────────────────────────────────────────
const char* ssid = "Wokwi-GUEST"; // WiFi name
const char* password = ""; // WiFi password
// ─────────────────────────────────────────────
// MQTT SETTINGS
// ─────────────────────────────────────────────
const char* mqtt_server = "broker.hivemq.com"; // public MQTT broker
// MQTT topics (channels)
const char* mqtt_topic_weather = "weather/suburbs"; // weather data
const char* mqtt_topic_summary = "weather/summary"; // summary data
const char* mqtt_topic_pollution = "weather/Pollution"; // pollution data
const char* mqtt_topic_alert = "alert/threshold"; // alert messages
const char* mqtt_topic_sensor = "alert/sensor"; // sensor readings
const char* mqtt_topic_threshold = "alert/set"; // slider input
const char* mqtt_topic_buzzer = "alert/buzzer"; // remote buzzer
const char* mqtt_pwm_topic = "Moola"; // LED brightness control
// ─────────────────────────────────────────────
// SENSOR SETUP
// ─────────────────────────────────────────────
#define DHTPIN 26 // DHT22 connected to GPIO 26
#define DHTTYPE DHT22 // sensor type
DHT dht(DHTPIN, DHTTYPE); // create sensor object
// ─────────────────────────────────────────────
// HARDWARE DEFINITIONS
// ─────────────────────────────────────────────
#define BUZZERPIN 15 // buzzer connected to GPIO 15
#define LEDPIN 13 // LED connected to GPIO 13
#define BUZZ_FREQ 1000 // buzzer tone (1kHz sound)
#define BUZZ_RESOLUTION 8 // PWM resolution
// ─────────────────────────────────────────────
// VARIABLES
// ─────────────────────────────────────────────
float USER_THRESHOLD = 30.0; // temperature limit set by user
float ALERT_THRESHOLD = 3.0; // deviation threshold
bool lastBuzzerState = false; // remembers buzzer state
// PWM settings (for LED brightness)
const int pwmChannel = 0;
const int pwmFreq = 5000;
const int pwmResolution = 8;
// ─────────────────────────────────────────────
// API KEY
// ─────────────────────────────────────────────
const char* apiKey = "1e4fad19e905cfb8553e1a004d2153d1"; // OpenWeather API key
// ─────────────────────────────────────────────
// STRUCT (used to store suburb info)
// ─────────────────────────────────────────────
struct Suburb {
const char* name; // suburb name
const char* query; // used in API request
float lat; // latitude
float lon; // longitude
};
// list of suburbs
Suburb suburbs[] = {
{"Bellville", "Bellville,ZA", 0, 0},
{"Belhar", "Belhar,ZA", 0, 0},
{"Cape Town CBD", NULL, -33.9249, 18.4241},
{"Kuilsrivier", "Kuilsrivier,ZA", 0, 0}
};
const int suburbCount = 4; // number of suburbs
// ─────────────────────────────────────────────
// NETWORK OBJECTS
// ─────────────────────────────────────────────
WiFiClient espClient; // WiFi connection
PubSubClient client(espClient); // MQTT client
// ─────────────────────────────────────────────
// BUZZER FUNCTIONS
// ─────────────────────────────────────────────
void buzzerOn() {
ledcWriteTone(BUZZERPIN, BUZZ_FREQ); // start sound
}
void buzzerOff() {
ledcWriteTone(BUZZERPIN, 0); // stop sound
}
// ─────────────────────────────────────────────
// CONNECT TO WIFI
// ─────────────────────────────────────────────
void connectWiFi() {
WiFi.begin(ssid, password); // start connection
while (WiFi.status() != WL_CONNECTED) {
delay(1000); // wait 1 second
Serial.println("Connecting to WiFi...");
}
Serial.println("WiFi Connected");
}
// ─────────────────────────────────────────────
// MQTT MESSAGE HANDLER
// ─────────────────────────────────────────────
void mqttCallback(char* topic, byte* payload, unsigned int length) {
String message = "";
// convert incoming bytes into a readable string
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.print("Received: ");
Serial.println(message);
// ───── THRESHOLD CONTROL
if (String(topic) == mqtt_topic_threshold) {
USER_THRESHOLD = message.toFloat(); // update user threshold
}
// ───── BUZZER REMOTE CONTROL
if (String(topic) == mqtt_topic_buzzer) {
if (message == "ON") {
buzzerOn();
} else if (message == "OFF") {
buzzerOff();
}
}
// ───── LED BRIGHTNESS CONTROL (PWM)
if (String(topic) == mqtt_pwm_topic) {
int duty = constrain(message.toInt(), 0, 255); // limit value
ledcWrite(pwmChannel, duty); // apply brightness
}
}
// ─────────────────────────────────────────────
// CONNECT TO MQTT
// ─────────────────────────────────────────────
void connectMQTT() {
while (!client.connected()) {
String clientId = "ESP32-" + String(random(0xffff), HEX);
if (client.connect(clientId.c_str())) {
// subscribe to incoming topics
client.subscribe(mqtt_topic_threshold);
client.subscribe(mqtt_topic_buzzer);
client.subscribe(mqtt_pwm_topic);
}
}
}
// ─────────────────────────────────────────────
// FETCH WEATHER FROM API
// ─────────────────────────────────────────────
float fetchWeather(Suburb s) {
HTTPClient http;
char url[200];
// build request URL
if (s.query != NULL) {
sprintf(url,
"https://api.openweathermap.org/data/2.5/weather?q=%s&appid=%s&units=metric",
s.query, apiKey);
}
http.begin(url); // start HTTP request
int code = http.GET(); // send GET request
// if successful
if (code == 200) {
StaticJsonDocument<1024> doc;
// parse JSON response
deserializeJson(doc, http.getString());
float temp = doc["main"]["temp"]; // extract temperature
// create MQTT message
char payload[200];
sprintf(payload,
"{\"suburb\":\"%s\",\"temp\":%.1f}",
s.name, temp);
client.publish(mqtt_topic_weather, payload); // send data
return temp;
}
return -999; // error value
}
// ─────────────────────────────────────────────
// SETUP (runs once at start)
// ─────────────────────────────────────────────
void setup() {
Serial.begin(115200); // start serial monitor
dht.begin(); // initialize sensor
// setup buzzer
ledcAttach(BUZZERPIN, BUZZ_FREQ, BUZZ_RESOLUTION);
// setup LED PWM
ledcSetup(pwmChannel, pwmFreq, pwmResolution);
ledcAttachPin(LEDPIN, pwmChannel);
connectWiFi(); // connect to internet
client.setServer(mqtt_server, 1883); // set MQTT broker
client.setCallback(mqttCallback); // set message handler
connectMQTT(); // connect to MQTT
}
// ─────────────────────────────────────────────
// MAIN LOOP (runs forever)
// ─────────────────────────────────────────────
void loop() {
client.loop(); // keep MQTT alive
if (!client.connected()) {
connectMQTT(); // reconnect if needed
}
float totalTemp = 0;
int count = 0;
// get weather for all suburbs
for (int i = 0; i < suburbCount; i++) {
float t = fetchWeather(suburbs[i]);
if (t != -999) { // valid data
totalTemp += t;
count++;
}
}
if (count > 0) {
float avgTemp = totalTemp / count; // calculate average
// read sensor
float temp = dht.readTemperature();
float hum = dht.readHumidity();
// retry if failed
if (isnan(temp)) {
delay(2000);
temp = dht.readTemperature();
}
if (!isnan(temp)) {
// check alert condition
bool alert = temp > USER_THRESHOLD;
// control buzzer locally
if (alert) {
buzzerOn();
} else {
buzzerOff();
}
// send sensor data
char payload[200];
sprintf(payload,
"{\"sensor_temp\":%.1f,\"humidity\":%.1f}",
temp, hum);
client.publish(mqtt_topic_sensor, payload);
// send alert message
if (alert) {
client.publish(mqtt_topic_alert, "{\"alert\":true}");
}
}
}
delay(30000); // wait 30 seconds
}