ESP8266 Internet Clock With Weather Update & Many More (No RTC)

by taifur in Circuits > Gadgets

70191 Views, 264 Favorites, 0 Comments

ESP8266 Internet Clock With Weather Update & Many More (No RTC)

cover1.jpg
IMG_4549.JPG
IMG_4584.JPG
IMG_4585.JPG

In this instructable, I am going to share how I made an ESP8266 based hackable Internat Clock. The clock is able to show:

  • Time (Using NTP),
  • Date,
  • Weather updates (e.g. temperature, humidity, pressure, clouds, sunrise, sunset),
  • Weather forecast,
  • Facebook like and notification,
  • Youtube subscriber counter
  • Customize message and many more

The firmware of the clock can be updated over wifi. No physical connection is required. Using the Network Time Protocol (NTP) for getting time. So, no RTC module or Arduino is required.

Bill of Materials

max7219-dot-matrix-module-4-in-1-display-1.jpg
967f67a4-15db-47a8-8b2d-8962d4b1c2a8.jpg
20170527144854_95497.jpg
20180109121214_73147.jpg

1. 1 X ESP8266-12E Module (Gearbest.com)

2. 2 X MAX7219 Dot Matrix Module 4 in One Display (Gearbest.com)

3. 1 X AMS1117 Power Supply Module (Gearbest.com)

4. 1 X DC 12V 1A Power Supply (Gearbest.com)

5. 1 X LM7805 Voltage regulator and jumper wires

Tools Require

1. Desktop 3D Printer: Buy one from Gearbest.com for only $129

2. Soldering Station (Gearbest.com)

3. Flexible Helping Arm for Soldering (optional) (Gearbest.com)

4. Lead-Free Solder Soldering Wire Reel (Gearbest.com)

5. Wire Cutters Clipper Diagonal Plier (Gearbest.com)

Network Time Protocol

2-1024x576.jpg

In a normal Arduino project, you would have to get a RTC module, set the right time, sacrifice some Arduino pins for communication. And when the RTC battery runs out, you have to replace it.

On the ESP8266, all you need is an Internet connection: you can just ask a time server what time it is. To do this, the Network Time Protocol (NTP) is used. NTP is based on UDP. There are a couple of differences, but it's really easy to use, thanks to the great libraries that come with the ESP8266 Arduino Core. The main difference between TCP and UDP is that TCP needs a connection to send messages:

First a handshake is sent by the client, the server responds, and a connection is established, and the client can send its messages. After the client has received the response of the server, the connection is closed (except when using WebSockets). To send a new message, the client has to open a new connection to the server first. This introduces latency and overhead. UDP doesn't use a connection, a client can just send a message to the server directly, and the server can just send a response message back to the client when it has finished processing. There is, however, no guarantee that the messages will arrive at their destination, and there's no way to know whether they arrived or not (without sending an acknowledgement, of course). This means that we can't halt the program to wait for a response, because the request or response packet could have been lost on the Internet, and the ESP8266 will enter an infinite loop. Instead of waiting for a response, we just send multiple requests, with a fixed interval between two requests, and just regularly check if a response has been received.

Getting the time
Let's take a look at an example that uses UDP to request the time from a NTP server. Libraries, constants and globals

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <WiFiUdp.h>

ESP8266WiFiMulti wifiMulti;      // Create an instance of the ESP8266WiFiMulti class, called 'wifiMulti'

WiFiUDP UDP;                     // Create an instance of the WiFiUDP class to send and receive

IPAddress timeServerIP;          // time.nist.gov NTP server address
const char* NTPServerName = "time.nist.gov";
const int NTP_PACKET_SIZE = 48;  // NTP time stamp is in the first 48 bytes of the message

byte NTPBuffer[NTP_PACKET_SIZE]; // buffer to hold incoming and outgoing packets

To use UDP, we have to include the WiFiUdp library,

and create a UDP object. We'll also need to allocate memory for a buffer to store the UDP packets. For NTP, we need a buffer of 48 bytes long. To know where to send the UDP packets to, we need the hostname of the NTP server, this is time.nist.gov.

void setup() {
  Serial.begin(115200);          // Start the Serial communication to send messages to the computer
  delay(10);
  Serial.println("\r\n");

  startWiFi();                   // Try to connect to some given access points. Then wait for a connection

  startUDP();

  if(!WiFi.hostByName(NTPServerName, timeServerIP)) { // Get the IP address of the NTP server
    Serial.println("DNS lookup failed. Rebooting.");
    Serial.flush();
    ESP.reset();
  }
  Serial.print("Time server IP:\t");
  Serial.println(timeServerIP);
  
  Serial.println("\r\nSending NTP request ...");
  sendNTPpacket(timeServerIP);  
}

In the setup, we just start our Serial and Wi-Fi, as usual, and we start UDP as well. We'll look at the implementation of this function later. We need the IP address of the NTP server, so we perform a DNS lookup with the server's hostname. There's not much we can do without the IP address of the time server, so if the lookup fails, reboot the ESP. If we do get an IP, send the first NTP request, and enter the loop.

unsigned long intervalNTP = 60000; // Request NTP time every minute
unsigned long prevNTP = 0;
unsigned long lastNTPResponse = millis();
uint32_t timeUNIX = 0;

unsigned long prevActualTime = 0;

void loop() {
  unsigned long currentMillis = millis();

  if (currentMillis - prevNTP > intervalNTP) { // If a minute has passed since last NTP request
    prevNTP = currentMillis;
    Serial.println("\r\nSending NTP request ...");
    sendNTPpacket(timeServerIP);               // Send an NTP request
  }

  uint32_t time = getTime();                   // Check if an NTP response has arrived and get the (UNIX) time
  if (time) {                                  // If a new timestamp has been received
    timeUNIX = time;
    Serial.print("NTP response:\t");
    Serial.println(timeUNIX);
    lastNTPResponse = currentMillis;
  } else if ((currentMillis - lastNTPResponse) > 3600000) {
    Serial.println("More than 1 hour since last NTP response. Rebooting.");
    Serial.flush();
    ESP.reset();
  }

  uint32_t actualTime = timeUNIX + (currentMillis - lastNTPResponse)/1000;
  if (actualTime != prevActualTime && timeUNIX != 0) { // If a second has passed since last print
    prevActualTime = actualTime;
    Serial.printf("\rUTC time:\t%d:%d:%d   ", getHours(actualTime), getMinutes(actualTime), getSeconds(actualTime));
  }  
}

The first part of the loop sends a new NTP request to the time server every minute. This is based on Blink Without Delay. Then we call the getTime function to check if we've got a new response from the server. If this is the case, we update the timeUNIX variable with the new timestamp from the server. If we don't get any responses for an hour, then there's something wrong, so we reboot the ESP. The last part prints the actual time. The actual time is just the last NTP time plus the time since we received that NTP message.

Circuit Connection & Testing

clock-schematic.png
arduino1.png
arduino2.png

The main unit of the Clock is an ESP8266-12E chip. This chip is responsible to access time, date, weather and other relevant information from the server and display the data to the matrix display. For displaying the information I used 2, 4 in one dot matrix display connected as daisy-chain to form an 8 dot matrix display. On the display module, each matrix is driven by the MAX7219, a very popular led driver. One main advantage of having this display is that you can drive multiple displays using only three pins of any microcontroller. Another advantage is that a lot of Arduino library is available for MAX7219 and most of them support ESP8266.

The MAX7219 does all the control and refresh work for you in driving either an 8x8 matrix display or 8 x 7-segment displays (usually these also have a dot so it's really an 8-segment display) - 64 LEDs total. All you have to do is send it serial commands via the 4-pin SPI interface and it will automatically take care of the rest. Wiring is simplified as well, you only need to set the current level for all LEDs with a single resistor instead of 8 and you can also dim the entire display digitally. It's a thru-hole chip so you can use it in any breadboard, perfboard or other projects, although if you're soldering it in, we suggest using a socket.

One disadvantage of the circuit is that two voltage level is required for the clock. For stable operation, ESP8266 needs 3.3V supply and MAX7219 needs 5V supply. But it is not a big issue. A 3.3V regulator (AMS1117) and a 5V regulator LM7805 can solve the issue. ESP8266 is powered from 3.3V regulator and the display is powered from 5V regulator. Both the regulator is connected to a 12V DC supply.

I am assuming that you have some previous experience with ESP8266. If you are new in ESP8266 please take a look on getting started guide of esp8266. You may browse the following links:

https://randomnerdtutorials.com/getting-started-with-esp8266-wifi-transceiver-review/

https://openhomeautomation.net/getting-started-esp...

https://www.instructables.com/id/Getting-Started-Wi...

https://www.electronicshub.org/esp8266-arduino-interface/

I hope you are now confident enough to go forward. After making the circuit according to the above schematic upload the following Arduino sketch to the ESP8266-12E board. For uploading the first program a physical connection is required to your PC.

For compiling the sketch you will be required following libraries:

https://github.com/nickgammon/MAX7219_Dot_Matrix

https://github.com/nickgammon/bitBangedSPI

// Demo of MAX7219_Dot_Matrix library - sideways scrolling
// Author: Md. Khairul Alam
// Date: 5 July 2018
<p><br></p>

#include 
#include 
#include 

#include "Arduino.h"
#include 
#include 
const byte chips = 8; //number of matrix in the display

// for NodeMCU 1.0
#define DIN_PIN 15  // D8
#define CS_PIN  13  // D7
#define CLK_PIN 12  // D6

MAX7219_Dot_Matrix display (chips, CS_PIN, DIN_PIN, CLK_PIN);  // 2 chips, then specify the LOAD, DIN, CLK pins

WiFiClient client;

String weatherMain = "";
String weatherDescription = "";
String weatherLocation = "";
String country;
int humidity;
int pressure;
float temp;
float tempMin, tempMax;
int clouds;
float windSpeed;
String date;
int h,m,s;
int sunrise, sunset;

String currencyRates;
String weatherString;

// =======================================================================
// CHANGE YOUR CONFIG HERE:
// =======================================================================
const char* ssid     = "taifur&mafi";     // SSID of local network
const char* password = "University";   // Password on network
String weatherKey = "deb88f35850ad1216f787bc647c4939f";
String weatherLang = "&lang=en";
String cityID = "1185241"; //Dhaka
// read OpenWeather api description for more info
// =======================================================================

char message1 [] = "This is a testing display. Designed by Md. Khairul Alam";
char weather_message[300];
char time_message[50];
char date_message[50];

void setup ()
  {
  display.begin ();
  display.setIntensity (1);

  Serial.begin(115200);
  Serial.print("Connecting WiFi ");
  WiFi.begin(ssid, password);
  //printStringWithShift("Connecting",15);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected: "); Serial.println(WiFi.localIP());

  getWeatherData();
  String str = weatherString;
  int str_len = str.length() + 1; 
  str.toCharArray(weather_message, str_len) ;   

  getTime();
  
}  // end of setup

unsigned long lastMoved = 0;
unsigned long MOVE_INTERVAL = 2;  // mS
int  messageOffset;

void updateDisplay (char *msg)
  {
  //String message = msg;
  display.sendSmooth (msg, messageOffset);
  
  // next time show one pixel onwards
  if (messageOffset++ >= (int) (strlen (msg) * 8))
    messageOffset = - chips * 8;
  }  // end of updateDisplay
  
long last = millis();
long lastTime = millis();

void loop () 
  { 
  if((millis()- last)>1000*60*15){  //update weather every 15 minutes 
    getWeatherData();
    getTime();
    String str = weatherString;
    int str_len = str.length() + 1;
    str.toCharArray(weather_message, str_len) ;
    last = millis();
   }
   
  // update display if time is up
  if(s>0 && s<40){
  if (millis () - lastMoved >= MOVE_INTERVAL)
    {
      updateDisplay(weather_message);
      lastMoved = millis ();
    }  
  }

  if(s>40 && s<55){
  if (millis () - lastMoved >= MOVE_INTERVAL)
    {
      updateDisplay(date_message);
      lastMoved = millis ();
    }  
  }
  /*
  // do other stuff here    
  if(millis()-lastTime>65000){ 
    long now = millis();
    do{
      updateTime();
      showTime();
      }while(millis()-now<40000);
    now = millis();
    lastTime = millis();
  }  
   */
   if(s>55){
      showTime(); 
    }
   
 updateTime();
       
}  // end of loop


// =======================================================================
// retrive weather data

const char *weatherHost = "api.openweathermap.org";

void getWeatherData()
{
  Serial.print("connecting to "); Serial.println(weatherHost);
  if (client.connect(weatherHost, 80)) {
    client.println(String("GET /data/2.5/weather?id=") + cityID + "&units=metric&appid=" + weatherKey + weatherLang + "\r\n" +
                "Host: " + weatherHost + "\r\nUser-Agent: ArduinoWiFi/1.1\r\n" +
                "Connection: close\r\n\r\n");
  } else {
    Serial.println("connection failed");
    return;
  }
  String line;
  int repeatCounter = 0;
  while (!client.available() && repeatCounter < 10) {
    delay(500);
    Serial.println("w.");
    repeatCounter++;
  }
  while (client.connected() && client.available()) {
    char c = client.read(); 
    if (c == '[' || c == ']') c = ' ';
    line += c;
  }

  client.stop();

  DynamicJsonBuffer jsonBuf;
  JsonObject &root = jsonBuf.parseObject(line);
  if (!root.success())
  {
    Serial.println("parseObject() failed");
    return;
  }
  //weatherMain = root["weather"]["main"].as();
  weatherDescription = root["weather"]["description"].as();
  weatherDescription.toLowerCase();
  Serial.println(weatherDescription);
  //  weatherLocation = root["name"].as();
  //  country = root["sys"]["country"].as();
  sunrise = root["sys"]["sunrise"];
  sunset = root["sys"]["sunset"];
  temp = root["main"]["temp"];
  humidity = root["main"]["humidity"];
  pressure = root["main"]["pressure"];
  tempMin = root["main"]["temp_min"];
  tempMax = root["main"]["temp_max"];
  windSpeed = root["wind"]["speed"];
  clouds = root["clouds"]["all"];
  //String deg = String(char('~')+131);
  //weatherString = "  Temp: " + String(temp,1) /*+ deg*/ + "C (" + String(tempMin,1) /*+ deg*/ + "-" + String(tempMax,1) /*+ deg*/ + ")  ";
  weatherString = "  Temp: " + String(temp,1) /*+ deg*/ + "C";
  //weatherString += weatherDescription;
  weatherString += "  Humidity: " + String(humidity) + "% ";
  weatherString += "  Pressure: " + String(pressure) + "hPa ";
  weatherString += "  Clouds: " + String(clouds) + "%  ";
  weatherString += "  Wind: " + String(windSpeed,1) + "m/s   ";
  Serial.println(weatherString);
}

// =======================================================================
// retrive curency rate

const char* currencyHost = "cinkciarz.pl";

void getCurrencyRates()
{
  WiFiClientSecure client;
  Serial.print("connecting to "); Serial.println(currencyHost);
  if (!client.connect(currencyHost, 443)) {
    Serial.println("connection failed");
    return;
  }
  client.print(String("GET / HTTP/1.1\r\n") +
               "Host: " + currencyHost + "\r\nConnection: close\r\n\r\n");

  //Serial.print("request sent");
  int repeatCounter = 0;
  while (!client.available() && repeatCounter < 10) {
    delay(500);
    Serial.println("c.");
    repeatCounter++;
  }
  Serial.println("connected");
  while (client.connected() && client.available()) {
    String line = client.readStringUntil('\n');
    //      Serial.println(line);
    int currIdx = line.indexOf("/kantor/kursy-walut-cinkciarz-pl/usd");
    if (currIdx > 0) {
      String curr = line.substring(currIdx + 33, currIdx + 33 + 3);
      curr.toUpperCase();
      line = client.readStringUntil('\n');
      int rateIdx = line.indexOf("\">");
      if (rateIdx <= 0) {
        Serial.println("Found rate but wrong structure!");
        return;
      }
      currencyRates = "        PLN/" + curr + ": ";
      if (line[rateIdx - 1] == 'n') currencyRates += char('~'+24); else currencyRates += char('~'+23); // down/up
      currencyRates += line.substring(rateIdx + 2, rateIdx + 8) + " ";

      line = client.readStringUntil('\n');
      rateIdx = line.indexOf("\">");
      if (rateIdx <= 0) {
        Serial.println("Found rate but wrong structure!");
        return;
      }
      if (line[rateIdx - 1] == 'n') currencyRates += char('~'+24); else currencyRates += char('~'+23); // down/up
      currencyRates += line.substring(rateIdx + 2, rateIdx + 8);
      currencyRates.replace(',', '.');
      break;
    }
  }
  client.stop();
}

// =======================================================================

float utcOffset = 2;
long localEpoc = 0;
long localMillisAtUpdate = 0;

void getTime()
{
  WiFiClient client;
  if (!client.connect("www.google.com", 80)) {
    Serial.println("connection to google failed");
    return;
  }

  client.print(String("GET / HTTP/1.1\r\n") +
               String("Host: <a> <a> <a>  www.google.com\r\n")  </a> </a> </a> +
               String("Connection: close\r\n\r\n"));
  int repeatCounter = 0;
  while (!client.available() && repeatCounter < 10) {
    delay(500);
    //Serial.println(".");
    repeatCounter++;
  }

  String line;
  client.setNoDelay(false);
  while(client.connected() && client.available()) {
    line = client.readStringUntil('\n');
    line.toUpperCase();
    if (line.startsWith("DATE: ")) {
      date = ""+line.substring(6, 22);
      h = line.substring(23, 25).toInt();
      m = line.substring(26, 28).toInt();
      s = line.substring(29, 31).toInt();
      localMillisAtUpdate = millis();
      localEpoc = (h * 60 * 60 + m * 60 + s);

      String clock_date = date;
      int date_len = clock_date.length() + 1; 
      clock_date.toCharArray(date_message, date_len) ;
    
    }
  }
  client.stop();
}

// =======================================================================

void updateTime()
{
  long curEpoch = localEpoc + ((millis() - localMillisAtUpdate) / 1000);
  long epoch = round(curEpoch + 3600 * utcOffset + 86400L) % 86400L;
  h = ((epoch  % 86400L) / 3600) % 24;
  m = (epoch % 3600) / 60;
  s = epoch % 60;
}

// =======================================================================
long lastmillis=millis();
void showTime()
{
    String timeString = "";
    timeString+= h/10 ? h/10 : 0;
    timeString+= h%10;
    timeString+= ":";
    timeString+= m/10;
    timeString+= m%10;
    timeString+= ":";
    timeString+= s/10;
    timeString+= s%10;   
    String clock_time = timeString;
    int clock_len = clock_time.length() + 1; 
    clock_time.toCharArray(time_message, clock_len) ;
    display.sendString(time_message);
}

Arduino Sketch with OTA Support
#include 
#include 
#include 
#include 

#include 
#include 
#include 

#include "Arduino.h"
#include 

const byte chips = 8; //number of matrix in the display

// for NodeMCU 1.0
#define DIN_PIN 15  // D8
#define CS_PIN  13  // D7
#define CLK_PIN 12  // D6

MAX7219_Dot_Matrix display (chips, CS_PIN, DIN_PIN, CLK_PIN);  // 2 chips, then specify the LOAD, DIN, CLK pins

WiFiClient client;

String weatherMain = "";
String weatherDescription = "";
String weatherLocation = "";
String country;
int humidity;
int pressure;
float temp;
float tempMin, tempMax;
int clouds;
float windSpeed;
String date;
int h,m,s;
int sunrise, sunset;

String currencyRates;
String weatherString;

const char* ssid = "taifur&mafi";
const char* password = "University";

String weatherKey = "deb88f35850ad1216f787bc647c4939f";
String weatherLang = "&lang=en";
String cityID = "1185241"; //Dhaka
// read OpenWeather api description for more info
// =======================================================================

char message1 [] = "This is a testing display. Designed by Md. Khairul Alam";
char weather_message[300];
char time_message[50];
char date_message[50];

int LED = 2;

void setup() {
  Serial.begin(115200);

  display.begin ();
  display.setIntensity (1);
  Serial.print("Connecting WiFi ");
  Serial.println("Booting");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }

  // Port defaults to 8266
  // ArduinoOTA.setPort(8266);

  // Hostname defaults to esp8266-[ChipID]
  // ArduinoOTA.setHostname("myesp8266");

  // No authentication by default
  // ArduinoOTA.setPassword("admin");

  // Password can be set with it's md5 value as well
  // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");

  ArduinoOTA.onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH)
      type = "sketch";
    else // U_SPIFFS
      type = "filesystem";

    // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
    Serial.println("Start updating " + type);
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });
  ArduinoOTA.begin();
  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  pinMode(LED, OUTPUT);     // Initialize the LED_BUILTIN pin as an output

  getWeatherData();
  String str = weatherString;
  int str_len = str.length() + 1; 
  str.toCharArray(weather_message, str_len) ;   

  getTime();
}

unsigned long lastMoved = 0;
unsigned long MOVE_INTERVAL = 2;  // mS
int  messageOffset;

void updateDisplay (char *msg)
  {
  //String message = msg;
  display.sendSmooth (msg, messageOffset);
  
  // next time show one pixel onwards
  if (messageOffset++ >= (int) (strlen (msg) * 8))
    messageOffset = - chips * 8;
  }  // end of updateDisplay
  
long last = millis();
long lastTime = millis();

void loop() {
  ArduinoOTA.handle();

  if((millis()- last)>1000*60*15){  //update weather every 15 minutes 
    getWeatherData();
    getTime();
    String str = weatherString;
    int str_len = str.length() + 1;
    str.toCharArray(weather_message, str_len) ;
    last = millis();
   }
   
  // update display if time is up
  if(s>0 && s<40){
  if (millis () - lastMoved >= MOVE_INTERVAL)
    {
      updateDisplay(weather_message);
      lastMoved = millis ();
    }  
  }

  if(s>40 && s<55){
  if (millis () - lastMoved >= MOVE_INTERVAL)
    {
      updateDisplay(date_message);
      lastMoved = millis ();
    }  
  }
  /*
  // do other stuff here    
  if(millis()-lastTime>65000){ 
    long now = millis();
    do{
      updateTime();
      showTime();
      }while(millis()-now<40000);
    now = millis();
    lastTime = millis();
  }  
   */
   if(s>55){
      showTime(); 
    }
   
 updateTime();                  // Wait for two seconds (to demonstrate the active low LED)
}

// =======================================================================
// retrive weather data

const char *weatherHost = "api.openweathermap.org";

void getWeatherData()
{
  Serial.print("connecting to "); Serial.println(weatherHost);
  if (client.connect(weatherHost, 80)) {
    client.println(String("GET /data/2.5/weather?id=") + cityID + "&units=metric&appid=" + weatherKey + weatherLang + "\r\n" +
                "Host: " + weatherHost + "\r\nUser-Agent: ArduinoWiFi/1.1\r\n" +
                "Connection: close\r\n\r\n");
  } else {
    Serial.println("connection failed");
    return;
  }
  String line;
  int repeatCounter = 0;
  while (!client.available() && repeatCounter < 10) {
    delay(500);
    Serial.println("w.");
    repeatCounter++;
  }
  while (client.connected() && client.available()) {
    char c = client.read(); 
    if (c == '[' || c == ']') c = ' ';
    line += c;
  }

  client.stop();

  DynamicJsonBuffer jsonBuf;
  JsonObject &root = jsonBuf.parseObject(line);
  if (!root.success())
  {
    Serial.println("parseObject() failed");
    return;
  }
  //weatherMain = root["weather"]["main"].as();
  weatherDescription = root["weather"]["description"].as();
  weatherDescription.toLowerCase();
  Serial.println(weatherDescription);
  //  weatherLocation = root["name"].as();
  //  country = root["sys"]["country"].as();
  sunrise = root["sys"]["sunrise"];
  sunset = root["sys"]["sunset"];
  temp = root["main"]["temp"];
  humidity = root["main"]["humidity"];
  pressure = root["main"]["pressure"];
  tempMin = root["main"]["temp_min"];
  tempMax = root["main"]["temp_max"];
  windSpeed = root["wind"]["speed"];
  clouds = root["clouds"]["all"];
  //String deg = String(char('~')+131);
  //weatherString = "  Temp: " + String(temp,1) /*+ deg*/ + "C (" + String(tempMin,1) /*+ deg*/ + "-" + String(tempMax,1) /*+ deg*/ + ")  ";
  weatherString = "  Temp: " + String(temp,1) /*+ deg*/ + "C";
  //weatherString += weatherDescription;
  weatherString += "  Humidity: " + String(humidity) + "% ";
  weatherString += "  Pressure: " + String(pressure) + "hPa ";
  weatherString += "  Clouds: " + String(clouds) + "%  ";
  weatherString += "  Wind: " + String(windSpeed,1) + "m/s   ";
  Serial.println(weatherString);
}

// =======================================================================
// retrive curency rate

const char* currencyHost = "cinkciarz.pl";

void getCurrencyRates()
{
  WiFiClientSecure client;
  Serial.print("connecting to "); Serial.println(currencyHost);
  if (!client.connect(currencyHost, 443)) {
    Serial.println("connection failed");
    return;
  }
  client.print(String("GET / HTTP/1.1\r\n") +
               "Host: " + currencyHost + "\r\nConnection: close\r\n\r\n");

  //Serial.print("request sent");
  int repeatCounter = 0;
  while (!client.available() && repeatCounter < 10) {
    delay(500);
    Serial.println("c.");
    repeatCounter++;
  }
  Serial.println("connected");
  while (client.connected() && client.available()) {
    String line = client.readStringUntil('\n');
    //      Serial.println(line);
    int currIdx = line.indexOf("/kantor/kursy-walut-cinkciarz-pl/usd");
    if (currIdx > 0) {
      String curr = line.substring(currIdx + 33, currIdx + 33 + 3);
      curr.toUpperCase();
      line = client.readStringUntil('\n');
      int rateIdx = line.indexOf("\">");
      if (rateIdx <= 0) {
        Serial.println("Found rate but wrong structure!");
        return;
      }
      currencyRates = "        PLN/" + curr + ": ";
      if (line[rateIdx - 1] == 'n') currencyRates += char('~'+24); else currencyRates += char('~'+23); // down/up
      currencyRates += line.substring(rateIdx + 2, rateIdx + 8) + " ";

      line = client.readStringUntil('\n');
      rateIdx = line.indexOf("\">");
      if (rateIdx <= 0) {
        Serial.println("Found rate but wrong structure!");
        return;
      }
      if (line[rateIdx - 1] == 'n') currencyRates += char('~'+24); else currencyRates += char('~'+23); // down/up
      currencyRates += line.substring(rateIdx + 2, rateIdx + 8);
      currencyRates.replace(',', '.');
      break;
    }
  }
  client.stop();
}

// =======================================================================

float utcOffset = 2;
long localEpoc = 0;
long localMillisAtUpdate = 0;

void getTime()
{
  WiFiClient client;
  if (!client.connect("www.google.com", 80)) {
    Serial.println("connection to google failed");
    return;
  }

  client.print(String("GET / HTTP/1.1\r\n") +
               String("Host: <a> <a>  www.google.com\r\n")  </a> </a> +
               String("Connection: close\r\n\r\n"));
  int repeatCounter = 0;
  while (!client.available() && repeatCounter < 10) {
    delay(500);
    //Serial.println(".");
    repeatCounter++;
  }

  String line;
  client.setNoDelay(false);
  while(client.connected() && client.available()) {
    line = client.readStringUntil('\n');
    line.toUpperCase();
    if (line.startsWith("DATE: ")) {
      date = ""+line.substring(6, 22);
      h = line.substring(23, 25).toInt();
      m = line.substring(26, 28).toInt();
      s = line.substring(29, 31).toInt();
      localMillisAtUpdate = millis();
      localEpoc = (h * 60 * 60 + m * 60 + s);

      String clock_date = date;
      int date_len = clock_date.length() + 1; 
      clock_date.toCharArray(date_message, date_len) ;
    
    }
  }
  client.stop();
}

// =======================================================================

void updateTime()
{
  long curEpoch = localEpoc + ((millis() - localMillisAtUpdate) / 1000);
  long epoch = round(curEpoch + 3600 * utcOffset + 86400L) % 86400L;
  h = ((epoch  % 86400L) / 3600) % 24;
  m = (epoch % 3600) / 60;
  s = epoch % 60;
}

// =======================================================================
long lastmillis=millis();
void showTime()
{
    String timeString = "";
    timeString+= h/10 ? h/10 : 0;
    timeString+= h%10;
    timeString+= ":";
    timeString+= m/10;
    timeString+= m%10;
    timeString+= ":";
    timeString+= s/10;
    timeString+= s%10;   
    String clock_time = timeString;
    int clock_len = clock_time.length() + 1; 
    clock_time.toCharArray(time_message, clock_len) ;
    display.sendString(time_message);
}

If you want to use the code with NTP server you will need the following additional libraries to compile the code:

Time.h & TimeLib.h: https://github.com/PaulStoffregen/Time

Timezone.h: https://github.com/JChristensen/Timezone

NTPClient.h: https://github.com/arduino-libraries/NTPClient

To show your Facebook information in the display follow the link:

https://www.instructables.com/id/Facebook-Fan-Count/

https://www.hackster.io/helge-johnsen/facebook-fan-count-faf13e

To count your youtube subscriber please follow the instructable:

https://www.instructables.com/id/YouTube-Subscriber-Counter-With-ESP8266-V2/

3D Printing

tinkercad.png
IMG_4491.JPG
IMG_4508.JPG
IMG_4503.JPG

To give the display a professional look and make it long lasting I designed a 3D casing for the display. The box is designed in Tinkercad, a free online tool for 3D designing. You design is separated into four parts. Double print the bottom cover. Single print left and right side and attached two sides with super glue. Files for 3D printing is attached with the step. No support is required for printing.

Preparing the Display

IMG_4484.JPG
IMG_4489.JPG
IMG_4495.JPG
IMG_4505.JPG
IMG_4506.JPG
IMG_4515.JPG
IMG_4517.JPG

The first thing to do with the display modules is to connect two display modules together. The connection is very simple. Just attached the two displays and solder the identical pin together. Before soldering fix both the display together with some hot glue. After soldering both the display together connect jumper wire to the first display. These wires will be used to connect the display with the ESP8266 board.

Preparing the ESP8266-12E

IMG_4519.JPG
IMG_4520.JPG
IMG_4523.JPG
IMG_4525.JPG
IMG_4526.JPG
ESP_to_serial.png

The ESP8266-12E chip is the main brain and controller of the clock. At the same time, the chip collects the information from the server and drive the dot matrix display. For interfacing the display with the esp8266 module only three GPIO is required. I used GPIO 12, 13 and 15 of esp chip to connect the display with the chip.

A typical 8x8 Dot Matrix unit has 64 LEDs arranged in a plane. You can get your hands on two types of Dot Matrices. One which comes as a plain single matrix which has 16 pins to control the rows and columns of the array. This one would use a lot of wires and things can get a lot messier.

To simplify these things, it is also available integrated with MAX7219 Driver, which has 24 pins. In the end, you have 5 pins to connect to your I/O which makes your job a lot more easier. There are 16 output lines from the 7219 driving 64 individual LEDs. Persistence of vision is exploited to make the LEDs appear to be on all the time when in fact they are not. You can also control the brightness of the LEDs through the code.

The connection between ESP8266 and display module is as follows:

  • Vcc to 5V Pin of Display Module.
  • Gnd to Gnd Pin of the Display Module.
  • DIN to Digital Pin 15 of the ESP8266.
  • CS to Digital Pin 12of the ESP8266.
  • CLK to Digital Pin 13 of the ESP8266.

For first time programming of ESP module, I soldered jumpers wires to TX, RX pin of ESP module. VCC and CH_PD pin should be connected to VCC.

Inside the Box

IMG_4531.JPG
IMG_4532.JPG
IMG_4541.JPG
IMG_4544.JPG

In this stage, I will put all the circuits and modules inside the box. First, I place the display module in the box. Then I soldered ESP8266 to the display module according to the schematic. After that, I connect a 5V regulator LM7805 to the power pins of the display module. I also connect a 3.3V regulator module to the power pins of the ESP8266 module. Then I put hot glue to fix all the components in place. Finally, I place the bottom cover of the box.

Final Output

IMG_4549.JPG
IMG_4552.JPG
IMG_4554.JPG
IMG_4559.JPG
IMG_4563.JPG
IMG_4566.JPG
IMG_4557.JPG

After completing all the steps described above my clock is ready to hang. Before hanging the clock in the wall I tested it by powering it from a 12V wall adapter. After successfully testing and founding it ok I place the clock on the wall of my drawing room. The clock supports OTA update so I can update the firmware keeping the clock untouched.