29 abril 2018

Relógio com NodeMCU ESP8266 usando NTP

A imagem parece a mesma do post anterior sobre o NodeMCU ESP8266 com display Oled, mas o assunto aqui é outro: vamos ver como você pode criar um relógio com NodeMCU e Display Oled I2C, atualizando a hora através da internet via protocolo NTP sem precisar de nenhum hardware adicional.

Relógio com NodeMCU ESP8266 e display oled usando NTP

O módulo NodeMCU ESP8266 vai se conectar ao seu roteador wifi e efetuar uma conexão com o servidor NTP (um "servidor de horário"), atualizando em tempo real as informações no display. Você pode usar o mesmo princípio para criar outros projetos com o NodeMCU (ou outros módulos ESP8266), que envolvam a utilização de horários específicos, controle de tempo, etc.

O que é o NTP - Network Time Protocol


O NTP - Network Time Protocol - é um protocolo criado especialmente para sincronizar relógios com servidores na internet, usando UDP na porta 123. Uma estrutura de servidores NTP é dividida em camadas, ou estratos, numerados de 0 a 16. Essa estrutura basicamente tem como referência uma fonte de horário (um GPS ou um relógio atômico, por exemplo), e distribui essa informação para todos os computadores ou dispositivos que se conectam à ela.

Servidores NTP - Network Time Protocol
Imagem: xmodulo.com

E por que o NTP é importante? Os computadores e dispositivos, de certa maneira, tendem a ter um relógio não confiável: podem atrasar ou adiantar por características de hardware, ou ter o seu horário alterado acidentalmente pelo usuário ou até mesmo por vírus e outros programas. Em uma época em que tudo deve estar conectado e sincronizado, é de extrema importância garantir a integridade dos dados armazenados, seja para rodar um backup em um horário pré-estabelecido, gravar corretamente um log de acessos em um servidor ou até mesmo por questões de auditoria de dados.

Circuito relógio com NodeMCU e Display Oled


O nosso relógio com NodeMCU e Display Oled vai usar exatamente o mesmo circuito do post anterior. Vamos apenas alterar o programa para que este busque o horário atualizado no servidor NTP. Conecte então o seu display oled I2C no NodeMCU ESP8266 seguindo o esquema abaixo:

Circuito relógio com NodeMCU e display Oled I2C

Relembrando que a comunicação I2C será feita pelo pino D1 para o SDA e o D2 para o SCL, e a alimentação do display será de 3.3V.

Programa relógio com NodeMCU usando NTP


O programa abaixo foi baseado no programa original contido no guia A Beginner´s Guide to the ESP8266.  Ele usa basicamente as bibliotecas do ESP8266, mais a biblioteca SSD1306Wire.h mencionada no post anterior para configuração do display Oled.

Adicionei as funções de comunicação com o display e também os comandos para mostrar o horário no display (linhas 102 à 117). Antes de carregar o programa, você deve configurar as redes wifi disponíveis para o NodeMCU nas linhas 123 a 125, e pode também incluir mais redes, se necessário.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
//Programa: Relogio NTP com NodeMCU ESP8266 e display Oled
//Referencia: A Beginner's Guide to the ESP8266 - Pieter P.
//Adaptacoes e alteracoes: Arduino e Cia

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

//Cria uma instancia da classe ESP8266WiFiMulti, chamada 'wifiMulti'
ESP8266WiFiMulti wifiMulti;

//Cria uma instancia da classe WifiUDP para enviar e receber dados
WiFiUDP UDP;

IPAddress timeServerIP;
//Define o servidor NTP utilizado
const char* NTPServerName = "ntp.br";

//Time stamp do NTP se encontra nos primeiros 48 bytes da mensagem
const int NTP_PACKET_SIZE = 48;

//Buffer para armazenar os pacotes transmitidos e recebidos
byte NTPBuffer[NTP_PACKET_SIZE];

//Pinos do NodeMCU - Interface I2C: SDA => D1, SCL => D2

//Inicializa o display Oled
SSD1306Wire  display(0x3c, D1, D2);

void setup()
{
  Serial.begin(115200);
  delay(10);
  Serial.println("\r\n");

  //Inicia a comunicacao com os hothospts configurados
  startWiFi();
  startUDP();

  if (!WiFi.hostByName(NTPServerName, timeServerIP))
  {
    //Obtem o endereco IP do servidor NTP
    Serial.println("DNS lookup failed. Rebooting.");
    Serial.flush();
    ESP.reset();
  }
  Serial.print("IP do servidor NTP:\t");
  Serial.println(timeServerIP);

  Serial.println("\r\nEnviando requisicao NTP...");
  sendNTPpacket(timeServerIP);

  //Inicializacao do display
  display.init();
  display.flipScreenVertically();
}

//Requisita horario do servidor NTP a cada minuto
unsigned long intervalNTP = 60000;
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)
  {
    //Verificar se passou um minuto da ultima requisicao
    prevNTP = currentMillis;
    Serial.println("\r\nEnviando requisicao NTP ...");
    sendNTPpacket(timeServerIP);
  }

  uint32_t time = getTime();
  if (time)
  {
    timeUNIX = time - 10800;
    Serial.print("Resposta NTP:\t");
    Serial.println(timeUNIX);
    lastNTPResponse = currentMillis;
  } else if ((currentMillis - lastNTPResponse) > 3600000) {
    Serial.println("Mais de 1 hora desde a ultima resposta NTP. Reiniciando.");
    Serial.flush();
    ESP.reset();
  }

  uint32_t actualTime = timeUNIX + (currentMillis - lastNTPResponse) / 1000;
  if (actualTime != prevActualTime && timeUNIX != 0)
  {
    //Verifica se passou um segundo desde a ultima impressao de valores no serial monitor
    prevActualTime = actualTime;
    Serial.printf("\rUTC time:\t%d:%d:%d   ", getHours(actualTime), getMinutes(actualTime), getSeconds(actualTime));
    Serial.println();
  }

  //Mostrando a hora no display
  //Apaga o display
  display.clear();
  display.setTextAlignment(TEXT_ALIGN_CENTER);
  //Seleciona a fonte
  display.setFont(ArialMT_Plain_10);
  //Mostra o titulo na parte superior do display
  display.drawString(63, 10, "NTP Clock ESP8266");
  //Mostra o horario atualizado
  display.setFont(ArialMT_Plain_24);
  display.drawString(29, 29, String(getHours(actualTime)));
  display.drawString(45, 28, ":");
  display.drawString(62, 29, String(getMinutes(actualTime)));
  display.drawString(78, 28, ":");
  display.drawString(95, 29, String(getSeconds(actualTime)));
  display.display();
}

void startWiFi()
{
  //Coloque aqui as redes wifi necessarias
  wifiMulti.addAP("NOME_DA_REDE_WIFI_1", "SENHA_DA_REDE_WIFI_1");
  wifiMulti.addAP("NOME_DA_REDE_WIFI_2", "SENHA_DA_REDE_WIFI_2");
  wifiMulti.addAP("NOME_DA_REDE_WIFI_3", "SENHA_DA_REDE_WIFI_3");

  Serial.println("Conectando");
  while (wifiMulti.run() != WL_CONNECTED)
  {
    //Aguarda a conexao da rede wifi
    delay(250);
    Serial.print('.');
  }
  Serial.println("\r\n");
  Serial.print("Conectado a rede ");
  Serial.println(WiFi.SSID());
  Serial.print("Endereco IP:\t");
  Serial.print(WiFi.localIP());
  Serial.println("\r\n");
}

void startUDP()
{
  Serial.println("Iniciando UDP");
  //Inicializa UDP na porta 23
  UDP.begin(123);
  Serial.print("Porta local:\t");
  Serial.println(UDP.localPort());
  Serial.println();
}

uint32_t getTime()
{
  if (UDP.parsePacket() == 0)
  {
    return 0;
  }
  UDP.read(NTPBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
  //Combina os 4 bytes do timestamp em um numero de 32 bits
  uint32_t NTPTime = (NTPBuffer[40] << 24) | (NTPBuffer[41] << 16) | (NTPBuffer[42] << 8) | NTPBuffer[43];
  //Converte o horario NTP para UNIX timestamp
  //Unix time comeca em 1 de Jan de 1970. Sao 2208988800 segundos no horario NTP:
  const uint32_t seventyYears = 2208988800UL;
  //Subtrai setenta anos do tempo
  uint32_t UNIXTime = NTPTime - seventyYears;
  return UNIXTime;
}

void sendNTPpacket(IPAddress& address)
{
  //Seta todos os bytes do buffer como 0
  memset(NTPBuffer, 0, NTP_PACKET_SIZE);
  //Inicializa os valores necessarios para formar a requisicao NTP
  NTPBuffer[0] = 0b11100011;   // LI, Version, Mode
  //Envia um pacote requisitando o timestamp
  UDP.beginPacket(address, 123);
  UDP.write(NTPBuffer, NTP_PACKET_SIZE);
  UDP.endPacket();
}

inline int getSeconds(uint32_t UNIXTime) 
{
  return UNIXTime % 60;
}

inline int getMinutes(uint32_t UNIXTime) 
{
  return UNIXTime / 60 % 60;
}

inline int getHours(uint32_t UNIXTime) 
{
  return UNIXTime / 3600 % 24;
}

Carregado o programa, o NodeMCU vai se conectar na rede wifi disponível, abrir a conexão UDP e efetuar a requisição NTP. Todo o processo pode ser acompanhado pelo serial monitor:

Serial Monitor NodeMCU ESP8266

Após a conexão o horário será mostrado no display e atualizado à cada segundo.

Experimente também usar bibliotecas prontas para conexão NTP, como a NtpClientLib, e também veja opções de uso do protocolo NTP com Arduino, que você pode usar por exemplo com conexões ethernet ou shields wifi.

Nenhum comentário:

Postar um comentário