/*
* Cloudflare DDNS Updater for ESP8266
* Jefferson Ryan - automalabs.com.br
* Testado com IDE v1.8.1 e ESP8266 Core v2.3.0
*/
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>
#include <Ticker.h>
Ticker tick;
// Você precisa preencher estes valores ========================================================
//sua rede sem fio
const char* ssid = "";
const char* password = "";
//cloudflare
const String zone_id = "";
const String host_id = "";
const String host_name = "";
const String login_email = "";
const String api_key = "";
//================================================================================================
const char* cloudflare_host = "api.cloudflare.com";
//const String user_agent="Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0";
const String user_agent="ESP8266_DDNS_Updater"; //A Cloudflare exige um user_agent. É educado não mentir se não for indispensável.
const int httpsPort = 443;
char ipservice[] = "checkip.dyndns.com";
//Obtido através de debug. É diferente do exibido pelo browser
//Precisará ser atualizado manualmente quando o certificado expirar
const char* cloudflare_host_fingerprint = "6F 1F FB 38 B9 49 03 1E 84 AF DC 48 C8 81 84 45 1A 19 6F 70";
WiFiClientSecure secureClient;
WiFiClient UnsecureClient;
DynamicJsonBuffer jsonBuffer;
String ipExterno;
String ipRegistrado;
String ipAnterior;
boolean refresh=false;
const int maxIntervaloChecagens=10;
const int minIntervaloChecagens=2;
int minutos=0;
int intervaloChecagens=maxIntervaloChecagens;
void oneMinute() {
//oneMinute é uma callback. Executar algo complexo ("blocking") pode levar a resets do ESP8266
minutos++;
if (intervaloChecagens-minutos<1){
minutos=0;
refresh=true;
}
else{
int MinutosRestantes=intervaloChecagens-minutos;
Serial.print(MinutosRestantes);
Serial.println(" minutos para proxima checagem");
}
}
boolean GETMyIP(){
boolean result=true;
Serial.print("Conectando a ");
Serial.println(ipservice);
if (!UnsecureClient.connect(ipservice, 80)) {
Serial.println("Falha na conexao.");
result=false;
return result;
}
String url = "/";
UnsecureClient.print("GET " + url + " HTTP/1.1\n");
UnsecureClient.print("Host: " + (String)ipservice+"\n");
UnsecureClient.print("Connection: close\n");
UnsecureClient.print("User-Agent: "+user_agent+"\n");
UnsecureClient.print("\r\n\r\n");
Serial.print("Esperando resposta.");
int limiteEspera=500;
int count=0;
//TODO: Seria melhor usar um FOR aqui?
while (UnsecureClient.available() < 1) { //espero por uma resposta
count++;
Serial.print(".");
delay(100);
if (count>limiteEspera){
Serial.println("Desisti de esperar.");
Serial.println("Fechando Conexao.");
UnsecureClient.stop();
return false;
break;
}
}
Serial.println(""); //Separo dos "."
String line;
while(UnsecureClient.available()) //processo a resposta
{
line = UnsecureClient.readStringUntil('\n');
// Serial.println(line);
if (line.substring(0,5)=="<html"){
ipExterno= line.substring(line.indexOf(":")+2);
ipExterno= ipExterno.substring(0,ipExterno.indexOf("<"));
Serial.print("ipExterno: ");
Serial.println(ipExterno);
}
}
Serial.println("");
Serial.println("Fechando conexao.");
UnsecureClient.stop();
return result;
}
boolean conectarCloudflare(){
Serial.print("Conectando a ");
Serial.println(cloudflare_host);
if (!secureClient.connect(cloudflare_host, httpsPort)) {
Serial.println("A conexao falhou");
return false;
}
if (secureClient.verify(cloudflare_host_fingerprint, cloudflare_host)) {
Serial.println("O certificado confere.");
return true;
} else {
Serial.println("O certificado nao confere. Cancelando.");
secureClient.stop();
return false;
}
}
boolean SEND(String action){
boolean result=false;
if (!conectarCloudflare()){return result;};
String url = "/client/v4/zones/"+zone_id+"/dns_records/"+host_id;
Serial.print("Acessando URL: ");
Serial.println(url);
secureClient.print(action +" " + url + " HTTP/1.1\n");
secureClient.print("Host: " + (String)cloudflare_host+":443\n"); //TODO: colocar a porta em uma constante
secureClient.print("Connection: close\n");
secureClient.print("User-Agent: "+user_agent+"\n");
secureClient.print("X-Auth-Email: "+login_email+"\n");
secureClient.print("Content-Type: application/json\n");
secureClient.print("X-Auth-Key: "+api_key+"\n");
if (action=="PUT"){
//String original: {"type":"A","name":"r7.automalabs.com.br","content":"127.0.0.1","ttl":120,"proxied":false}
String postData = "{\"type\":\"A\",\"name\":\""+host_name+"\",\"content\":\""+ipExterno+"\",\"ttl\":120,\"proxied\":false}";
secureClient.print("Content-Length: ");
secureClient.print(postData.length());
secureClient.print("\r\n\r\n");
secureClient.print(postData);
}
else
{
secureClient.print("\r\n\r\n");
}
Serial.print(action);
Serial.print(" enviado. ");
Serial.print("Esperando resposta.");
int limiteEspera=500;
int count=0;
//TODO: Seria melhor usar um FOR aqui?
while (secureClient.available() < 1) { //espero por uma resposta
count++;
Serial.print(".");
delay(100);
if (count>limiteEspera){
Serial.println("Desisti de esperar.");
Serial.println("Fechando Conexao.");
secureClient.stop();
return false;
break;
}
}
Serial.println(""); //Separo dos "."
String line;
while(secureClient.available()) //processo a resposta
{
line = secureClient.readStringUntil('\n');
if (line.substring(2,8)=="result"){
Serial.println(line);
String ip= ParseJSON(line.substring(10)); //TODO: fazer processar toda a string
if (ip!=""){
ipRegistrado=ip;
result=true;
}
else{
result=false;
}
}
// Serial.println(line); //Util apenas para debug;
}
Serial.println("");
Serial.println("Fechando conexao.");
Serial.println("");
secureClient.stop();
return result;
}
String ParseJSON(String json){
JsonObject& root = jsonBuffer.parseObject(json);
if (!root.success()) {
Serial.println("parseObject() falhou");
return "";
}
//TODO: fazer o parse de "success" também
String nome = root["name"];
String ip = root["content"];
Serial.println(nome);
Serial.println(ip);
return ip;
}
void setup() {
tick.attach(60, oneMinute); //Executa oneMinute a cada 60 segundos
Serial.begin(115200);
//Serial.setDebugOutput(true);
Serial.println();
Serial.print("Conectando ao AP: ");
Serial.println(ssid);
Serial.print("Com a senha: ");
Serial.println(password);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi conectado.");
Serial.print("Endereco IP do ESP8266: ");
Serial.println(WiFi.localIP());
if (SEND("GET")){
compararEatualizar(ipRegistrado);
}
}
void loop() {
if (refresh) {
refresh=false;
compararEatualizar(ipAnterior);
}
}
void compararEatualizar(String ip) {
if (GETMyIP()){
if (ipExterno==ip){
Serial.println("Os IPs sao iguais.");
intervaloChecagens=maxIntervaloChecagens;
}
else
{ Serial.print("IP: ");
Serial.println(ip);
Serial.print("IP externo: ");
Serial.println(ipExterno);
Serial.println("Os IPs sao diferentes. Iniciando arualizacao.");
if (SEND("PUT")){
if (ipExterno!=ipRegistrado){
Serial.println("Erro ao atualizar o IP.");
Serial.print("IP na cloudflare: ");
Serial.println(ipRegistrado);
Serial.print("IP externo: ");
Serial.println(ipExterno);
intervaloChecagens=minIntervaloChecagens; //PUT falhou
}
else{
ipAnterior=ipExterno;
intervaloChecagens=maxIntervaloChecagens;
}
}
}
}
else
{
intervaloChecagens=minIntervaloChecagens; //GetMyIP falhou
}
Serial.print(intervaloChecagens);
Serial.println(" minutos para proxima checagem");
}