#include <WiFi.h> // Library to connect ESP32 to WiFi
#include <PubSubClient.h> // Library to connect ESP32 to MQTT broker
#include <HTTPClient.h> // Library to make HTTP GET requests to OpenWeather API
#include <ArduinoJson.h> // Library to parse the JSON response from OpenWeather
const char* ssid = "Wokwi-GUEST"; // WiFi network name
const char* password = ""; // WiFi password, empty for Wokwi
const char* mqtt_server = "broker.hivemq.com"; // Public MQTT broker address
const char* mqtt_topic = "Money"; // MQTT topic to publish weather data to
const char* apiKey = "1e4fad19e905cfb8553e1a004d2153d1"; // OpenWeather API key
WiFiClient espClient; // Creates a WiFi client object for network communication
PubSubClient client(espClient); // Creates an MQTT client using the WiFi client
void connectWiFi() {
WiFi.begin(ssid, password); // Start connecting to WiFi using the credentials
while (WiFi.status() != WL_CONNECTED) { // Keep looping until WiFi is connected
delay(1000); // Wait 1 second between each check
Serial.println("Connecting to WiFi..."); // Print status to serial monitor
}
Serial.println("WiFi Connected"); // Confirm connection once done
}
void connectMQTT() {
if (client.connected()) return; // Already connected, exit the function immediately — this prevents unnecessary reconnections
Serial.print("Attempting MQTT connection...");
String clientId = "ESP32Client-" + String(random(0xffff), HEX); // Generate a random unique client ID to avoid conflicts on the broker
if (client.connect(clientId.c_str())) { // Try to connect to the broker with the generated ID
Serial.println("Connected to MQTT broker"); // Confirm connection
} else {
Serial.print("Failed, state: ");
Serial.println(client.state()); // Print error code if connection failed
}
}
void fetchAndPublish(const char* suburbName, const char* query, float coordLat, float coordLon) {
// This function takes four inputs:
// suburbName = the display name published to Node-RED e.g. "Bellville"
// query = the name-based API query e.g. "Bellville,ZA", NULL if using coordinates
// coordLat = latitude, only used when query is NULL e.g. Cape Town CBD
// coordLon = longitude, only used when query is NULL e.g. Cape Town CBD
HTTPClient http; // Create an HTTP client object to make the API request
char url[200]; // Character array to hold the full API URL
if (query != NULL) {
sprintf(url, "https://api.openweathermap.org/data/2.5/weather?q=%s&appid=%s&units=metric", query, apiKey);
// If query is not NULL, build a name-based URL e.g. Bellville,ZA
// units=metric ensures temperature comes back in Celsius
} else {
sprintf(url, "https://api.openweathermap.org/data/2.5/weather?lat=%.4f&lon=%.4f&appid=%s&units=metric", coordLat, coordLon, apiKey);
// If query is NULL, build a coordinates-based URL using lat and lon
// This is used for Cape Town CBD because "Cape Town CBD" does not resolve by name on OpenWeather
}
http.begin(url); // Initialize the HTTP request with the built URL
int httpCode = http.GET(); // Send the GET request and store the HTTP response code
if (httpCode == 200) { // 200 means the API request was successful
String response = http.getString(); // Get the full JSON response as a string
StaticJsonDocument<1024> doc; // Create a JSON document with 1024 bytes allocated in memory
deserializeJson(doc, response); // Parse the JSON string into the document object
float temp = doc["main"]["temp"]; // Extract temperature in Celsius
float humidity = doc["main"]["humidity"]; // Extract humidity as a percentage
float lon = doc["coord"]["lon"]; // Extract longitude from response
float lat = doc["coord"]["lat"]; // Extract latitude from response
const char* desc = doc["weather"][0]["description"]; // Extract weather description from first element of the weather array
char payload[512]; // Character array to hold the final JSON message to publish
sprintf(payload,
"{\"suburb\":\"%s\"," // suburb display name
"\"temp\":\"%.1f°C\"," // temperature with °C label, 1 decimal place
"\"humidity\":\"%.1f%%\"," // humidity with % label, %% is needed to print a literal % in sprintf
"\"desc\":\"%s\"," // weather description
"\"lon\":%.4f," // longitude to 4 decimal places
"\"lat\":%.4f}", // latitude to 4 decimal places
suburbName, temp, humidity, desc, lon, lat); // insert actual values into the format
client.publish(mqtt_topic, payload); // Publish the JSON payload to the MQTT topic
Serial.println(payload); // Print the published message to serial monitor
} else {
Serial.print("HTTP Error for "); // Print error label if request failed
Serial.print(suburbName); // Print which suburb failed
Serial.print(": ");
Serial.println(httpCode); // Print the HTTP error code
}
http.end(); // Close the HTTP connection to free memory and resources
delay(1000); // Wait 1 second before the next suburb to avoid hitting the API rate limit
}
void setup() {
Serial.begin(115200); // Start serial monitor at 115200 baud rate
connectWiFi(); // Connect to WiFi
client.setServer(mqtt_server, 1883); // Set the MQTT broker address and port, 1883 is the default unencrypted MQTT port
connectMQTT(); // Connect to MQTT broker
}
void loop() {
client.loop(); // Must be called regularly to keep the MQTT connection alive and process any incoming messages
if (!client.connected()) { // Check if MQTT connection dropped
connectMQTT(); // Reconnect only if actually disconnected
}
fetchAndPublish("Bellville", "Bellville,ZA", 0, 0 ); // Fetch and publish Bellville using name query
fetchAndPublish("Belhar", "Belhar,ZA", 0, 0 ); // Fetch and publish Belhar using name query
fetchAndPublish("Cape Town CBD", NULL, -33.9249, 18.4241); // Fetch and publish Cape Town CBD using coordinates because name does not resolve
fetchAndPublish("Kuilsrivier", "Kuilsrivier,ZA", 0, 0 ); // Fetch and publish Kuilsrivier using name query
delay(30000); // Wait 30 seconds before fetching all four suburbs again
}