#define MMdebug
/**
* ESP8266 ESP-01 Clock and Weather Firmware
*
* This firmware uses an SSD1306 128x64 OLED display to show alternating screens:
* - Clock screen: shows day of week, current time (HH:MM), temperature & humidity, and date.
* - Weather screen: shows city name, current temperature, weather condition, humidity, wind speed, and pressure.
*
* Wi-Fi Configuration:
* If no configuration is found or if a button on GPIO3 is held at boot, the device
* starts as an access point (SSID: ESP01-Setup) and serves a configuration page.
* The page allows setting Wi-Fi SSID, password, city for weather, and timezone.
* Settings are saved in EEPROM and the device reboots to normal mode.
*
* Normal Operation:
* Connects to configured Wi-Fi and retrieves time via NTP (synchronized every 60s).
* Retrieves weather from wttr.in for the specified city (HTTPS GET request).
* Displays time and weather data on the OLED, switching screens every 15 seconds.
*
* Hardware:
* - ESP8266 ESP-01 (GPIO0=SDA, GPIO2=SCL for I2C OLED; GPIO3 as input for config button).
* - OLED display SSD1306 128x64 via I2C (address 0x3C).
*
* Note: Uses custom fonts (FreeMonoBold18pt7b for time, FreeMonoBold12pt7b for temperature).
* All other text uses default font. Display is rotated 180 degrees (setRotation(2)).
*/
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#elif defined(ESP32)
#include <WiFi.h>
#include <WebServer.h>
#endif
#include <EEPROM.h>
//#include <ESP8266WiFi.h>
//#include <ESP8266WebServer.h>
#include <WiFiClientSecure.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>
#include <Fonts/FreeMonoBold18pt7b.h> // MM: FreeMonoBold24pt7b
#include <Fonts/FreeMonoBold12pt7b.h>
#include <time.h>
#if defined(ESP8266)
// Pin definitions (ESP-01 / ESP8266):
const uint8_t SDA_PIN = 0; // I2C SDA connected to GPIO0
const uint8_t SCL_PIN = 2; // I2C SCL connected to GPIO2
const uint8_t BUTTON_PIN = 3; // Button on GPIO3 (RX pin)
#elif defined(ESP32)
// MM:
// Pin definitions (ESP32):
const uint8_t SDA_PIN = 21; // I2C SDA connected to GPIO0
const uint8_t SCL_PIN = 22; // I2C SCL connected to GPIO2
const uint8_t BUTTON_PIN = 16; // Button on GPIO3 (RX pin)
#endif
// EEPROM addresses for configuration data:
const int EEPROM_SIZE = 512;
const int ADDR_SSID = 0;
const int ADDR_PASS = 70;
const int ADDR_CITY = 140;
const int ADDR_TZ = 210; // 4 bytes (int32) for timezone offset in seconds
const int ADDR_SIGNATURE = 500; // 4-byte signature "CFG1" to indicate valid config
// Wi-Fi and server:
#if defined(ESP8266)
ESP8266WebServer server(80);
const char *AP_SSID = "MM-ESP01-Setup"; // Access Point SSID for config mode
#elif defined(ESP32)
// MM:
WebServer server(80);
const char *AP_SSID = "MM-ESP32-Setup"; // Access Point SSID for config mode
#endif
// Display:
Adafruit_SSD1306 display(128, 64, &Wire, -1);
bool displayInitialized = false;
// Time and weather:
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);
unsigned long lastScreenSwitch = 0;
bool showWeatherScreen = false;
bool displayReady = false;
unsigned long lastWeatherFetch = 0;
// Configuration variables:
#if defined(MMdebug)
// MM: for convenience: initial config without eprom
String wifiSSID = "Wokwi-GUEST";
String wifiPass = "";
String city = "Oerlikon";
int timezoneOffset = 2*60*60; // in seconds
#else
String wifiSSID = "";
String wifiPass = "";
String city = "";
int timezoneOffset = 0; // in seconds
#endif
// Weather data variables:
String weatherTemp = "N/A";
String weatherCond = "";
String weatherHum = "";
String weatherWind = "";
String weatherPress = "";
bool weatherValid = false;
// Function prototypes:
void loadSettings();
void saveSettings();
void startConfigPortal();
void handleConfigForm();
void drawTimeScreen();
void drawWeatherScreen();
bool getWeather();
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println(F("Booting..."));
// Initialize display
Wire.begin(SDA_PIN, SCL_PIN);
if (display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
displayInitialized = true;
display.setRotation(2);
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.print("Booting...");
display.display();
displayReady = true;
} else {
Serial.println(F("SSD1306 allocation failed"));
// Leave displayInitialized as false
}
pinMode(BUTTON_PIN, INPUT_PULLUP);
// Initialize EEPROM and load stored settings if available
EEPROM.begin(EEPROM_SIZE);
bool configMode = false;
///////////////////////////////
#if defined(MMdebug)
// MM: save settings for convenience, without init mode...
saveSettings();
#endif
//////////////////////////////////
// Check if config signature is present in EEPROM
if (EEPROM.read(ADDR_SIGNATURE) == 'C' &&
EEPROM.read(ADDR_SIGNATURE + 1) == 'F' &&
EEPROM.read(ADDR_SIGNATURE + 2) == 'G' &&
EEPROM.read(ADDR_SIGNATURE + 3) == '1') {
Serial.println(F("Config signature found in EEPROM."));
} else {
Serial.println(F("No config signature found (EEPROM uninitialized)."));
}
// Check button press in first 300ms of boot
bool buttonPressed = false;
unsigned long startTime = millis();
while (millis() - startTime < 300) {
if (digitalRead(BUTTON_PIN) == LOW) {
buttonPressed = true;
}
delay(10);
}
if (buttonPressed) {
Serial.println(F("Config button held - entering AP configuration mode."));
}
// Determine if we should start config portal
if (EEPROM.read(ADDR_SIGNATURE) != 'C' || EEPROM.read(ADDR_SIGNATURE+1) != 'F' ||
EEPROM.read(ADDR_SIGNATURE+2) != 'G' || EEPROM.read(ADDR_SIGNATURE+3) != '1' ||
buttonPressed) {
configMode = true;
}
if (configMode) {
// Load existing settings (if any) to pre-fill form
if (EEPROM.read(ADDR_SIGNATURE) == 'C' && EEPROM.read(ADDR_SIGNATURE + 1) == 'F' &&
EEPROM.read(ADDR_SIGNATURE + 2) == 'G' && EEPROM.read(ADDR_SIGNATURE + 3) == '1') {
loadSettings();
}
// Start configuration portal (Access Point mode)
startConfigPortal();
// After configuration, ESP will reboot. If not rebooted (e.g. user didn't submit),
// it will remain in AP mode and handleClient in loop.
} else {
// Load settings from EEPROM
loadSettings();
Serial.print(F("Connecting to WiFi: "));
Serial.println(wifiSSID);
WiFi.mode(WIFI_STA);
WiFi.begin(wifiSSID.c_str(), wifiPass.c_str());
// Wait up to 10 seconds for connection
unsigned long wifiStart = millis();
while (WiFi.status() != WL_CONNECTED && millis() - wifiStart < 10000) {
delay(500);
Serial.print('.');
}
Serial.println();
if (WiFi.status() == WL_CONNECTED) {
Serial.println(F("WiFi connected."));
Serial.print(F("IP Address: "));
Serial.println(WiFi.localIP());
} else {
Serial.println(F("WiFi connection failed. Starting AP mode instead."));
startConfigPortal();
return; // Exit setup to avoid running normal mode without WiFi
}
// Setup NTP client with stored timezone offset
timeClient.setPoolServerName("pool.ntp.org");
timeClient.setTimeOffset(timezoneOffset);
timeClient.setUpdateInterval(900000); // 15 mins interval
timeClient.begin();
// Perform initial NTP update
timeClient.update();
// Prepare first weather fetch
lastWeatherFetch = 0; // force immediate fetch on first weather screen display
weatherValid = false;
Serial.println(F("Setup complete, entering loop."));
}
Serial.println(F("Getting initial weather..."));
weatherValid = getWeather();
lastWeatherFetch = millis();
if (weatherValid) {
Serial.println(F("Initial weather fetch successful."));
} else {
Serial.println(F("Initial weather fetch failed."));
}
}
void loop() {
// If in config portal mode, handle web server
if (WiFi.getMode() == WIFI_AP) {
server.handleClient();
// In AP mode, do not run normal display loop
return;
}
// Regular mode: update time and handle display
timeClient.update();
unsigned long now = millis();
// Switch screen every 15 seconds
if (now - lastScreenSwitch > 15000) {
showWeatherScreen = !showWeatherScreen;
lastScreenSwitch = now;
// If switching to weather screen, update weather data (limit fetch frequency)
if (showWeatherScreen) {
// Update weather every 15 minutes
if (millis() - lastWeatherFetch > 900000UL || !weatherValid) {
Serial.println(F("Updating weather data..."));
weatherValid = getWeather();
lastWeatherFetch = millis();
if (weatherValid) {
Serial.println(F("Weather update successful."));
} else {
Serial.println(F("Weather update failed or data invalid."));
}
}
}
}
// Draw the appropriate screen
if (showWeatherScreen) {
drawWeatherScreen();
} else {
drawTimeScreen();
}
// Small delay to yield to system
delay(10);
}
void startConfigPortal() {
// Stop any existing WiFi and start AP
WiFi.disconnect();
WiFi.mode(WIFI_AP);
WiFi.softAP(AP_SSID);
IPAddress apIP = WiFi.softAPIP();
Serial.print(F("Started AP mode with SSID "));
Serial.print(AP_SSID);
Serial.print(F(". Connect and browse to http://"));
Serial.println(apIP);
if (displayReady) {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("AP mode");
display.setCursor(0, 10);
//MM:
// display.println("SSID: MM-ESP01-Setup");
display.print("SSID: ");
display.println(AP_SSID);
display.setCursor(0, 20);
display.println("IP: 192.168.4.1");
display.display();
}
// Setup web server routes
server.on("/", HTTP_GET, []() {
// HTML page for config
String page = "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
page += String("<title>ESP8266 Setup</title><style>") +
".container{max-width:300px;margin:40px auto;padding:20px;background:#f7f7f7;border:1px solid #ccc;border-radius:5px;}" +
"body{text-align:center;font-family:sans-serif;}h2{margin-bottom:15px;}label{display:block;text-align:left;margin-top:10px;}" +
"input, select{width:100%;padding:8px;margin-top:5px;border:1px solid #ccc;border-radius:3px;}" +
"input[type=submit]{margin-top:15px;background:#4caf50;color:white;border:none;cursor:pointer;border-radius:3px;font-size:16px;}" +
"input[type=submit]:hover{background:#45a049;}" +
"</style></head><body><div class='container'>";
page += "<h2>Device Configuration</h2><form method='POST' action='/'>";
// WiFi SSID field
page += "<label>Wi-Fi SSID:</label><input type='text' name='ssid' value='" + wifiSSID + "' required>";
// Password field
page += "<label>Password:</label><input type='password' name='pass' value='" + wifiPass + "' placeholder=''>";
// City field
page += "<label>City:</label><input type='text' name='city' value='" + city + "' required>";
// Timezone dropdown
page += "<label>Timezone:</label><select name='tz'>";
// Populate timezone options from UTC-12 to UTC+14
for (int tzHour = -12; tzHour <= 14; ++tzHour) {
long tzSeconds = tzHour * 3600;
String option = "<option value='" + String(tzSeconds) + "'";
if (tzSeconds == timezoneOffset) {
option += " selected";
}
option += ">UTC";
if (tzHour >= 0) option += "+" + String(tzHour);
else option += String(tzHour);
option += "</option>";
page += option;
}
page += "</select>";
// Submit button
page += "<input type='submit' value='Save'></form></div></body></html>";
server.send(200, "text/html", page);
});
server.on("/", HTTP_POST, handleConfigForm);
server.begin();
Serial.println(F("HTTP server started for config portal."));
}
void handleConfigForm() {
// Get form values
String ssid = server.arg("ssid");
String pass = server.arg("pass");
String newCity = server.arg("city");
String tzStr = server.arg("tz");
Serial.println(F("Received configuration:"));
Serial.print(F("SSID: ")); Serial.println(ssid);
Serial.print(F("Password: ")); Serial.println(pass);
Serial.print(F("City: ")); Serial.println(newCity);
Serial.print(F("Timezone (s): ")); Serial.println(tzStr);
if (ssid.length() > 0 && newCity.length() > 0 && tzStr.length() > 0) {
wifiSSID = ssid;
wifiPass = pass;
city = newCity;
timezoneOffset = tzStr.toInt();
// Save to EEPROM
saveSettings();
// Send response page
server.send(200, "text/html", "<html><body><h3>Settings saved. Rebooting...</h3></body></html>");
delay(1000);
ESP.restart();
} else {
server.send(400, "text/html", "<html><body><h3>Invalid input, please fill all required fields.</h3></body></html>");
}
}
void loadSettings() {
// Read SSID
uint8_t len = EEPROM.read(ADDR_SSID);
if (len > 0 && len < 0xFF) {
char buf[80];
for (int i = 0; i < len; ++i) {
buf[i] = char(EEPROM.read(ADDR_SSID + 1 + i));
}
buf[len] = '\0';
wifiSSID = String(buf);
} else {
wifiSSID = "";
}
// Read Password
len = EEPROM.read(ADDR_PASS);
if (len > 0 && len < 0xFF) {
char buf[80];
for (int i = 0; i < len; ++i) {
buf[i] = char(EEPROM.read(ADDR_PASS + 1 + i));
}
buf[len] = '\0';
wifiPass = String(buf);
} else {
wifiPass = "";
}
// Read City
len = EEPROM.read(ADDR_CITY);
if (len > 0 && len < 0xFF) {
char buf[80];
for (int i = 0; i < len; ++i) {
buf[i] = char(EEPROM.read(ADDR_CITY + 1 + i));
}
buf[len] = '\0';
city = String(buf);
} else {
city = "";
}
// Read Timezone offset (int32)
uint32_t b0 = EEPROM.read(ADDR_TZ);
uint32_t b1 = EEPROM.read(ADDR_TZ + 1);
uint32_t b2 = EEPROM.read(ADDR_TZ + 2);
uint32_t b3 = EEPROM.read(ADDR_TZ + 3);
uint32_t raw = (b0 & 0xFF) | ((b1 & 0xFF) << 8) | ((b2 & 0xFF) << 16) | ((b3 & 0xFF) << 24);
timezoneOffset = (int) raw;
}
void saveSettings() {
// Write SSID
uint8_t len = wifiSSID.length();
if (len > 0) {
EEPROM.write(ADDR_SSID, len);
for (int i = 0; i < len; ++i) {
EEPROM.write(ADDR_SSID + 1 + i, wifiSSID[i]);
}
} else {
EEPROM.write(ADDR_SSID, 0);
}
// Write Password
len = wifiPass.length();
if (len > 0) {
EEPROM.write(ADDR_PASS, len);
for (int i = 0; i < len; ++i) {
EEPROM.write(ADDR_PASS + 1 + i, wifiPass[i]);
}
} else {
EEPROM.write(ADDR_PASS, 0);
}
// Write City
len = city.length();
if (len > 0) {
EEPROM.write(ADDR_CITY, len);
for (int i = 0; i < len; ++i) {
EEPROM.write(ADDR_CITY + 1 + i, city[i]);
}
} else {
EEPROM.write(ADDR_CITY, 0);
}
// Write Timezone (int32)
int tz = timezoneOffset;
EEPROM.write(ADDR_TZ, tz & 0xFF);
EEPROM.write(ADDR_TZ + 1, (tz >> 8) & 0xFF);
EEPROM.write(ADDR_TZ + 2, (tz >> 16) & 0xFF);
EEPROM.write(ADDR_TZ + 3, (tz >> 24) & 0xFF);
// Write signature 'CFG1'
EEPROM.write(ADDR_SIGNATURE, 'C');
EEPROM.write(ADDR_SIGNATURE + 1, 'F');
EEPROM.write(ADDR_SIGNATURE + 2, 'G');
EEPROM.write(ADDR_SIGNATURE + 3, '1');
// Commit changes to EEPROM
EEPROM.commit();
Serial.println(F("Configuration saved to EEPROM."));
}
void drawTimeScreen() {
if (!displayInitialized) {
return;
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
// Day name at top center
// Get current epoch time and derive day of week
time_t epoch = timeClient.getEpochTime();
// Calculate day of week (0=Sunday .. 6=Saturday)
struct tm *tm = gmtime(&epoch);
int wday = tm->tm_wday; // tm_wday: days since Sunday (0-6)
String dayName = "";
switch(wday) {
case 0: dayName = "Sonntag"; break;
case 1: dayName = "Montag"; break;
case 2: dayName = "Dienstag"; break;
case 3: dayName = "Mittwoch"; break;
case 4: dayName = "Donnerstag"; break;
case 5: dayName = "Freitag"; break;
case 6: dayName = "Samstag"; break;
}
display.setFont(NULL); // default font
// display.setFont(&FreeMonoBold12pt7b);
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(dayName, 0, 0, &x1, &y1, &w, &h);
// center horizontally
int dayX = (128 - w) / 2;
display.setCursor(dayX, 0); //MM: 16 statt 0 bei 12pt
display.print(dayName);
// Time HH:MM in large font, centered
display.setFont(&FreeMonoBold18pt7b);
// Format time as HH:MM
char timeBuf[6];
snprintf(timeBuf, sizeof(timeBuf), "%02d:%02d", timeClient.getHours(), timeClient.getMinutes());
String timeStr = String(timeBuf);
display.getTextBounds(timeStr, 0, 30, &x1, &y1, &w, &h);
int timeX = (128 - w) / 2;
// Vertically center the text around mid (y=32)
int timeY = 32 + (h / 2); // MM 38+ ...
display.setCursor(timeX, timeY);
display.print(timeStr);
// Bottom left: temperature and humidity
display.setFont(NULL);
display.setCursor(0, 56);
if (weatherValid && weatherTemp != "N/A" && weatherHum != "") {
String tempDisplay = weatherTemp;
tempDisplay.trim();
display.print(tempDisplay);
display.print((char)247); // degree symbol
display.print("C ");
display.print(weatherHum);
} else {
display.print("N/A");
}
// Bottom right: date dd.mm.yyyy
tm = gmtime(&epoch);
int day = tm->tm_mday;
int month = tm->tm_mon + 1;
int year = tm->tm_year + 1900;
char dateBuf[12];
snprintf(dateBuf, sizeof(dateBuf), "%02d.%02d.%04d", day, month, year);
String dateStr = String(dateBuf);
display.getTextBounds(dateStr, 0, 0, &x1, &y1, &w, &h);
display.setCursor(128 - w, 56);
display.print(dateStr);
display.display();
}
void drawWeatherScreen() {
if (!displayInitialized) {
return;
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setFont(NULL);
// Top center: city name
String cityName = city;
int16_t x1, y1; uint16_t w, h;
display.getTextBounds(cityName, 0, 0, &x1, &y1, &w, &h);
int cityX = (128 - w) / 2;
display.setCursor(cityX, 0);
display.print(cityName);
// Middle center: temperature (big font FreeMonoBold12pt7b)
display.setFont(&FreeMonoBold12pt7b);
if (weatherValid && weatherTemp != "N/A") {
// weatherTemp is e.g. "+12" or "-3" as string (cleaned)
String tempNum = weatherTemp;
// Center the numeric part
display.getTextBounds(tempNum, 0, 30, &x1, &y1, &w, &h);
int tempX = (128 - (w + 12)) / 2; // leave space for degree and C (~12px)
int tempY = 30; // baseline for 12pt font around mid screen
display.setCursor(tempX, tempY);
display.print(tempNum);
// Now draw degree symbol and 'C' in default font after the number
//display.setFont(NULL);
// Place small degree symbol near top of big text and 'C' after it
int degX = tempX + w + 3; // position degree just right of number
int degY = tempY - 16; // raise small text (approx half big font height)
if (degY < 0) degY = 0;
display.setCursor(degX, degY);
//display.print((char)247);
display.drawCircle(degX, degY, 2, SSD1306_WHITE);
display.setCursor(degX + 3, tempY);
display.setFont(&FreeMonoBold12pt7b);
display.print("C");
display.setFont(NULL);
} else {
// If weather not available, show N/A in big font
String na = "N/A";
display.getTextBounds(na, 0, 30, &x1, &y1, &w, &h);
int naX = (128 - w) / 2;
display.setCursor(naX, 30);
display.print(na);
// No degree symbol or 'C' in this case
}
// Below temperature: condition string (centered)
display.setFont(NULL);
String cond = weatherValid ? weatherCond : "";
cond.trim();
display.getTextBounds(cond, 0, 40, &x1, &y1, &w, &h);
int condX = (128 - w) / 2;
display.setCursor(condX, 40);
display.print(cond);
// Bottom right: H:% W:m/s P:mm
display.setFont(NULL);
if (weatherValid && weatherTemp != "N/A") {
String bottomStr = String("H:") + weatherHum;
bottomStr += " W:" + weatherWind;
if (weatherWind != "N/A") bottomStr += "m/s";
bottomStr += " P:" + weatherPress + "mm";
display.getTextBounds(bottomStr, 0, 56, &x1, &y1, &w, &h);
display.setCursor(128 - w, 56);
display.print(bottomStr);
} else {
String bottomStr = "H:N/A W:N/A P:N/A";
display.getTextBounds(bottomStr, 0, 56, &x1, &y1, &w, &h);
display.setCursor(128 - w, 56);
display.print(bottomStr);
}
display.display();
}
bool getWeather() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println(F("WiFi not connected, cannot get weather."));
return false;
}
WiFiClientSecure client;
client.setInsecure(); // Skip certificate validation for simplicity
const char* host = "wttr.in";
// MM: added https and explicit host
// String url = "https://wttr.in/" + city + "?lang=de&format=%25t|%25C|%25h|%25w|%25P";
String url = "/" + city + "?lang=de&format=%25t|%25C|%25h|%25w|%25P";
Serial.print(F("Connecting to weather server: "));
Serial.println(host);
// MM: extra debugging
int conerr;
conerr=client.connect(host, 443);
if (!conerr) {
Serial.println(F("Connection failed."));
Serial.println(conerr);
return false;
}
Serial.print(F("Sending Request for URL: "));
Serial.println(url);
// Send GET request
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"User-Agent: MM-ESP8266\r\n" +
"Connection: close\r\n\r\n");
// Read full response as raw text
String response = "";
unsigned long timeout = millis() + 5000;
while (millis() < timeout && client.connected()) {
while (client.available()) {
char c = client.read();
response += c;
}
}
client.stop();
Serial.println(F("---- RAW RESPONSE ----"));
Serial.println(response);
Serial.println(F("----------------------"));
// --- Extract line with weather data ---
String result = "";
int from = 0;
while (from >= 0) {
int to = response.indexOf('\n', from);
if (to == -1) break;
String line = response.substring(from, to);
line.replace("\r", "");
line.trim();
Serial.print("DEBUG LINE: >");
Serial.print(line);
Serial.println("<");
if (line.indexOf('|') != -1) {
result = line;
break;
}
from = to + 1;
}
// If no \n at the end — catch the remaining part
if (result.length() == 0 && from < response.length()) {
String line = response.substring(from);
line.replace("\r", "");
line.trim();
Serial.print("FALLBACK LINE: >");
Serial.print(line);
Serial.println("<");
if (line.indexOf('|') != -1) {
result = line;
}
}
Serial.print(F("Weather raw response: "));
Serial.println(result);
if (result.length() == 0) {
Serial.println(F("No weather data found."));
return false;
}
// Parse fields: temp|cond|hum|wind|press
int idx1 = result.indexOf('|');
int idx2 = result.indexOf('|', idx1 + 1);
int idx3 = result.indexOf('|', idx2 + 1);
int idx4 = result.indexOf('|', idx3 + 1);
if (idx1 < 0 || idx2 < 0 || idx3 < 0 || idx4 < 0) return false;
String tempStr = result.substring(0, idx1);
String condStr = result.substring(idx1 + 1, idx2);
String humStr = result.substring(idx2 + 1, idx3);
String windStr = result.substring(idx3 + 1, idx4);
String pressStr = result.substring(idx4 + 1);
// Clean Temperature: remove degree symbol and 'C'
tempStr.replace("°C", "");
tempStr.replace("°", "");
tempStr.replace("C", "");
tempStr.trim();
weatherTemp = tempStr; // keep + or - sign if present
// Clean Condition:
condStr.trim();
weatherCond = condStr;
// Clean Humidity: ensure '%' present
humStr.trim();
if (!humStr.endsWith("%")) {
weatherHum = humStr + "%";
} else {
weatherHum = humStr;
}
// Clean Wind: extract numeric part (km/h)
String windNum = "";
for (uint i = 0; i < windStr.length(); ++i) {
char c = windStr.charAt(i);
if ((c >= '0' && c <= '9') || c == '.') {
windNum += c;
} else if (c == ' ' && windNum.length() > 0) {
break;
}
}
if (windNum.length() == 0) {
weatherWind = "N/A";
} else {
float windKmh = windNum.toFloat();
float windMs = windKmh / 3.6;
int windMsRounded = (int)round(windMs);
weatherWind = String(windMsRounded);
}
// Clean Pressure: extract numeric part (hPa)
String pressNum = "";
for (uint i = 0; i < pressStr.length(); ++i) {
char c = pressStr.charAt(i);
if ((c >= '0' && c <= '9') || c == '.') {
pressNum += c;
} else if (!pressNum.isEmpty()) {
break;
}
}
if (pressNum.length() == 0) {
weatherPress = "N/A";
} else {
float pressHpa = pressNum.toFloat();
float pressMm = pressHpa * 0.75006;
int pressRounded = (int) round(pressMm);
weatherPress = String(pressRounded);
}
Serial.println(F("Parsed weather data:"));
Serial.print(F("Temp=")); Serial.println(weatherTemp);
Serial.print(F("Cond=")); Serial.println(weatherCond);
Serial.print(F("Hum=")); Serial.println(weatherHum);
Serial.print(F("Wind(m/s)=")); Serial.println(weatherWind);
Serial.print(F("Pressure(mmHg)=")); Serial.println(weatherPress);
// Validate critical fields
if (weatherTemp == "" || weatherCond == "") {
return false;
}
return true;
}
Push during startup
for setup mode
(skipped in emulator)