#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
#include <TimeLib.h>
#include <Fonts/FreeSansBold18pt7b.h>
#include <ArduinoJson.h>
#define I2C_ADDRESS 0x3C
// Custom weather icons (16x16 pixels)
const uint8_t clearSkyIcon[] PROGMEM = {
0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b00000011, 0b11000000,
0b00001111, 0b11110000,
0b00011111, 0b11111000,
0b00111111, 0b11111100,
0b00111111, 0b11111100,
0b01111111, 0b11111110,
0b01111111, 0b11111110,
0b01111111, 0b11111110,
0b00111111, 0b11111100,
0b00111111, 0b11111100,
0b00011111, 0b11111000,
0b00001111, 0b11110000,
0b00000011, 0b11000000,
0b00000000, 0b00000000
};
const uint8_t fewCloudsIcon[] PROGMEM = {
0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b00000011, 0b11000000,
0b00001111, 0b11110000,
0b00011111, 0b11111000,
0b00111111, 0b11111100,
0b00111111, 0b11111100,
0b01111111, 0b11111110,
0b01111111, 0b11111110,
0b01111111, 0b11111110,
0b00111111, 0b11111100,
0b00111100, 0b00111100,
0b00011000, 0b00011000,
0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b00000000, 0b00000000
};
const uint8_t scatteredCloudsIcon[] PROGMEM = {
0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b00000011, 0b11000000,
0b00001111, 0b11110000,
0b00011111, 0b11111000,
0b00111111, 0b11111100,
0b00111111, 0b11111100,
0b01111111, 0b11111110,
0b01111111, 0b11111110,
0b01111111, 0b11111110,
0b00111111, 0b11111100,
0b00000000, 0b00000000,
0b00000000, 0b00000000
};
const uint8_t rainIcon[] PROGMEM = {
0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b00000011, 0b11000000,
0b00001111, 0b11110000,
0b00011111, 0b11111000,
0b00111111, 0b11111100,
0b00111111, 0b11111100,
0b01111111, 0b11111110,
0b01111111, 0b11111110,
0b01111111, 0b11111110,
0b00101010, 0b10101000,
0b00010101, 0b01010000,
0b00101010, 0b10101000,
0b00010101, 0b01010000,
0b00000000, 0b00000000,
0b00000000, 0b00000000
};
const uint8_t thunderstormIcon[] PROGMEM = {
0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b00000011, 0b11000000,
0b00001111, 0b11110000,
0b00011111, 0b11111000,
0b00111111, 0b11111100,
0b00111111, 0b11111100,
0b01111111, 0b11111110,
0b01111111, 0b11111110,
0b01111111, 0b11111110,
0b00111100, 0b00111100,
0b00011000, 0b00011000,
0b00110000, 0b00001100,
0b00011000, 0b00011000,
0b00001100, 0b00110000,
0b00000000, 0b00000000
};
const uint8_t snowIcon[] PROGMEM = {
0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b00000011, 0b11000000,
0b00001111, 0b11110000,
0b00011111, 0b11111000,
0b00111111, 0b11111100,
0b00111111, 0b11111100,
0b01111111, 0b11111110,
0b01111111, 0b11111110,
0b01111111, 0b11111110,
0b00101010, 0b10101000,
0b00010101, 0b01010000,
0b00101010, 0b10101000,
0b00010101, 0b01010000,
0b00000000, 0b00000000,
0b00000000, 0b00000000
};
const uint8_t mistIcon[] PROGMEM = {
0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b01111111, 0b11111110,
0b01111111, 0b11111110,
0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b01111111, 0b11111110,
0b01111111, 0b11111110,
0b00000000, 0b00000000,
0b00000000, 0b00000000,
0b00000000, 0b00000000
};
Adafruit_SH1107 display = Adafruit_SH1107(64, 128, &Wire);
const char* ssid = "Your SSID";
const char* password = "Your SSID password";
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 0, 60000);
// OpenWeatherMap API settings
const String apiKey = "Your API key"; // Your API key
const String weatherUrl = "http://api.openweathermap.org/data/2.5/weather?lat=57.19316&lon=-2.11010&units=metric&appid=" + apiKey;
float temperature = 0.0; // Variable to store temperature
bool temperatureFetchSuccess = false; // Track if temperature fetch was successful
unsigned long lastTemperatureFetchTime = 0; // Track the last time the temperature was fetched
const unsigned long temperatureFetchInterval = 5 *60 * 1000; // Fetch temperature every 5 minutes (in milliseconds)
const unsigned long retryInterval = 5 * 1000; // Retry after 5 seconds if parsing fails
bool colonVisible = true; // Track whether the colon is visible
unsigned long lastColonToggleTime = 0; // Track the last time the colon was toggled
String weatherIcon = ""; // Variable to store the weather icon code
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
Serial.print("Connecting to Wi-Fi");
// Wi-Fi connection with timeout
unsigned long startAttemptTime = millis();
while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 10000) { // 10-second timeout
delay(500);
Serial.print(".");
}
if (WiFi.status() != WL_CONNECTED) {
Serial.println("Failed to connect to Wi-Fi!");
} else {
Serial.println("Connected!");
}
// Display initialization with error handling
if (!display.begin(I2C_ADDRESS, true)) {
Serial.println("Display initialization failed!");
while (1); // Halt execution
}
display.setRotation(1); // Rotate display for landscape mode
display.clearDisplay();
timeClient.begin();
}
void loop() {
timeClient.update();
time_t rawTime = timeClient.getEpochTime();
setTime(rawTime);
// Clear the entire display to avoid overlapping text
display.clearDisplay();
// Display time in 24-hour format at (2, 30)
display.setTextColor(SH110X_WHITE);
display.setFont(&FreeSansBold18pt7b);
char timeStr[6];
sprintf(timeStr, "%02d%02d", hour(), minute()); // 24-hour format without colon
display.setCursor(0, 31); // Adjusted position
display.print(timeStr[0]); // First digit of hour
display.print(timeStr[1]); // Second digit of hour
// Toggle colon visibility every second
if (millis() - lastColonToggleTime >= 1000) {
colonVisible = !colonVisible;
lastColonToggleTime = millis();
}
// Display colon if visible
if (colonVisible) {
display.setCursor(38, 30); // Position for colon
display.print(":");
} else {
display.fillRect(40, 10, 10, 20, SH110X_BLACK); // Overwrite colon space to prevent shifting
}
display.setCursor(48, 30); // Maintain fixed spacing
display.print(timeStr[2]); // First digit of minute
display.print(timeStr[3]); // Second digit of minute
// Display date at (20, 45)
display.setFont();
display.setTextSize(1);
char dateStr[20];
sprintf(dateStr, "%s %02d/%02d/%04d", dayShortStr(weekday()), day(), month(), year());
display.setCursor(20, 45); // Adjusted position
display.print(dateStr);
// Draw Wi-Fi connection status (3 circles)
drawWiFiStatus();
// Fetch and display outdoor temperature at (95, 20)
unsigned long currentTime = millis();
if (currentTime - lastTemperatureFetchTime >= temperatureFetchInterval ||
(!temperatureFetchSuccess && currentTime - lastTemperatureFetchTime >= retryInterval)) {
fetchTemperature();
lastTemperatureFetchTime = currentTime; // Update the last fetch time
}
displayTemperature();
// Draw weather icon at (95, 10)
if (weatherIcon.length() > 0) {
drawWeatherIcon(weatherIcon);
}
// Update the display
display.display();
delay(500); // Update every 500ms
}
void drawWiFiStatus() {
if (WiFi.status() == WL_CONNECTED) {
// Draw 3 circles for connected status
display.drawCircle(126, 1, 6, SH110X_WHITE); // Outer circle
display.drawCircle(127, 0, 3, SH110X_WHITE); // Middle circle
display.drawCircle(128, 0, 1, SH110X_WHITE); // Inner circle
} else {
// Draw an "X" for disconnected status
display.drawLine(115, 0, 125, 10, SH110X_WHITE); // Diagonal line 1
display.drawLine(125, 0, 115, 10, SH110X_WHITE); // Diagonal line 2
}
}
void fetchTemperature() {
if (WiFi.status() == WL_CONNECTED) {
WiFiClient client;
if (client.connect("api.openweathermap.org", 80)) {
// Send HTTP GET request
client.print("GET " + weatherUrl + " HTTP/1.1\r\n");
client.print("Host: api.openweathermap.org\r\n");
client.print("Connection: close\r\n\r\n");
// Wait for the response
while (client.connected() && !client.available()) {
delay(10);
}
// Read the response
String jsonResponse = "";
while (client.available()) {
jsonResponse += client.readString();
}
client.stop();
// Debug: Print the raw JSON response
Serial.println("Raw JSON Response:");
Serial.println(jsonResponse);
// Find the start of the JSON object
int jsonStart = jsonResponse.indexOf('{');
if (jsonStart == -1) {
Serial.println("JSON start not found!");
temperatureFetchSuccess = false;
return;
}
// Extract the JSON object
String jsonObject = jsonResponse.substring(jsonStart);
// Debug: Print the extracted JSON object
Serial.println("Extracted JSON Object:");
Serial.println(jsonObject);
// Parse the JSON object
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, jsonObject);
if (error) {
Serial.println("Failed to parse JSON!");
Serial.println(error.c_str());
temperatureFetchSuccess = false;
return;
}
// Check if the "main" and "temp" fields exist
if (doc.containsKey("main") && doc["main"].containsKey("temp")) {
temperature = doc["main"]["temp"].as<float>(); // Extract temperature
temperatureFetchSuccess = true;
Serial.println("Temperature fetched successfully: " + String(temperature));
} else {
Serial.println("Temperature data not found in JSON!");
temperatureFetchSuccess = false;
}
// Extract weather icon code
if (doc.containsKey("weather") && doc["weather"].is<JsonArray>()) {
JsonArray weatherArray = doc["weather"].as<JsonArray>();
if (weatherArray.size() > 0 && weatherArray[0].containsKey("icon")) {
weatherIcon = weatherArray[0]["icon"].as<String>();
Serial.println("Weather icon code: " + weatherIcon);
} else {
Serial.println("Weather icon code not found!");
}
}
} else {
Serial.println("Failed to connect to OpenWeatherMap!");
temperatureFetchSuccess = false;
}
} else {
Serial.println("Wi-Fi not connected!");
temperatureFetchSuccess = false;
}
}
void displayTemperature() {
display.setFont();
display.setTextSize(1);
display.setCursor(92, 25); // Adjusted position
if (temperatureFetchSuccess) {
display.print(temperature, 1); // Display temperature with 1 decimal place
display.print((char)247); // Degrees symbol (ASCII 247)
display.print("C"); // Celsius symbol
} else {
display.print(" "); // Display "N/A" if temperature fetch failed
}
}
void drawWeatherIcon(const String& iconCode) {
const uint8_t* icon = nullptr;
if (iconCode == "01d" || iconCode == "01n") {
icon = clearSkyIcon; // Clear sky
} else if (iconCode == "02d" || iconCode == "02n") {
icon = fewCloudsIcon; // Few clouds
} else if (iconCode == "03d" || iconCode == "03n") {
icon = scatteredCloudsIcon; // Scattered clouds
} else if (iconCode == "04d" || iconCode == "04n") {
icon = scatteredCloudsIcon; // Broken clouds (same as scattered)
} else if (iconCode == "09d" || iconCode == "09n") {
icon = rainIcon; // Shower rain
} else if (iconCode == "10d" || iconCode == "10n") {
icon = rainIcon; // Rain (same as shower rain)
} else if (iconCode == "11d" || iconCode == "11n") {
icon = thunderstormIcon; // Thunderstorm
} else if (iconCode == "13d" || iconCode == "13n") {
icon = snowIcon; // Snow
} else if (iconCode == "50d" || iconCode == "50n") {
icon = mistIcon; // Mist
} else {
// Unknown weather
return;
}
// Draw the icon at (97, 2)
display.drawBitmap(97, 2, icon, 16, 16, SH110X_WHITE);
}
I had an Adafruit Huzzah and an OLED FeatherWing 128 x 64 in my box of goodies and decided to make a smart digital clock for my study.
The clock is connected to internet via WiFi, synchronises the time and via an api displays the temperature in my area. It is set to update every 5 minutes. Also displays an icon of the current weather.
These are parsed from the api response:
{"coord":{"lon":yyyyyy,"lat":xxxxx},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"base":"stations","main":{"temp":1.03,"feels_like":-1.33,"temp_min":0.57,"temp_max":2.64,"pressure":1039,"humidity":76,"sea_level":1039,"grnd_level":1034},"visibility":10000,"wind":{"speed":2.06,"deg":170},"clouds":{"all":0},"dt":1738790391,"sys":{"type":1,"id":1440,"country":"GB","sunrise":1738742403,"sunset":1738773892},"timezone":0,"id":3333224,"name":"xxxxxx City","cod":200}
Next I created a 3D printed bezel that allowed me to fit the stacked boards in my workbench pannel.
This page (Digital Clock with WiFi and Weather (Huzzah & 128x64OLED)) was last updated on March 05, 2025.
Text editor powered by tinymce.