//////////////////////////////////////////////////////////////////////////////////////////////////////////// //Arquivo: MQTT_WiFiManager_Rele.ino //Tipo: Exemplo de uso das bibliotecas WiFiManager e PubSubClient para ESP8266 na IDE do Arduino //Autor: Marco Rabelo para o canal Infortronica Para Zumbis (www.youtube.com/c/InfortronicaParaZumbis) //Descricao: Curso de ESP8266 - Utilizando MQTT e gerenciador de wifi para controlar um relé. //Video: https://youtu.be/oX4ttJEULmA //////////////////////////////////////////////////////////////////////////////////////////////////////////// /* Equivalencia das saidas Digitais entre NodeMCU e ESP8266 (na IDE do Arduino) NodeMCU - ESP8266 D0 = 16; D1 = 5; D2 = 4; D3 = 0; D4 = 2; D5 = 14; D6 = 12; D7 = 13; D8 = 15; D9 = 3; D10 = 1; */ #include //Esta precisa ser a primeira referência, ou nada dará certo e sua vida será arruinada. kkk #include //https://github.com/esp8266/Arduino #include #include #include //https://github.com/tzapu/WiFiManager #include //https://github.com/bblanchon/ArduinoJson #include #include #define DEBUG //Se descomentar esta linha vai habilitar a 'impressão' na porta serial //Coloque os valores padrões aqui, porém na interface eles poderão ser substituídos. #define servidor_mqtt "m13.cloudmqtt.com" //URL do servidor MQTT #define servidor_mqtt_porta "14110" //Porta do servidor (a mesma deve ser informada na variável abaixo) #define servidor_mqtt_usuario "xuzwzjvh" //Usuário #define servidor_mqtt_senha "-J2u-urkjI0t" //Senha #define mqtt_topico_sub_lamp_sala "esp8266/pincmd/sala" //Tópico para subscrever o comando a ser dado no D5 declarado abaixo #define mqtt_topico_sub_lamp_cozinha "esp8266/pincmd/cozinha" //Tópico para subscrever o comando a ser dado no D0 declarado abaixo //Declaração do D5 e D0 que será utilizado e a memória alocada para armazenar o status deste D5 e D0 na EEPROM #define D5 14 //D5 que executara a acao dado no topico "esp8266/pincmd/sala" e terá seu status informado no tópico "esp8266/pinstatus" #define D0 16 //D0 que executara a acao dado no topico "esp8266/pincmd/cozinha" e terá seu status informado no tópico "esp8266/pinstatus" #define memoria_alocadaD5 4 //Define o quanto sera alocado na EEPROM (valores entre 4 e 4096 bytes) #define memoria_alocadaD0 4 //Define o quanto sera alocado na EEPROM (valores entre 4 e 4096 bytes) WiFiClient espClient; //Instância do WiFiClient PubSubClient client(espClient); //Passando a instância do WiFiClient para a instância do PubSubClient uint8_t statusAntD5 = 0; //Variável que armazenará o status do pino que foi gravado anteriormente na EEPROM do D5 uint8_t statusAntD0 = 0; //Variável que armazenará o status do pino que foi gravado anteriormente na EEPROM do D0 bool precisaSalvarD5 = false; //Flag para salvar os dados D5 bool precisaSalvarD0 = false; //Flag para salvar os dados D0 //Função para imprimir na porta serial void imprimirSerial(bool linha, String mensagem){ #ifdef DEBUG if(linha){ Serial.println(mensagem); }else{ Serial.print(mensagem); } #endif } //Função de retorno para notificar sobre a necessidade de salvar as configurações do D5 void precisaSalvarD5Callback() { imprimirSerial(true, "As configuracoes tem que ser salvas."); precisaSalvarD5 = true; } //Função de retorno para notificar sobre a necessidade de salvar as configurações do D0 void precisaSalvarD0Callback() { imprimirSerial(true, "As configuracoes tem que ser salvas."); precisaSalvarD0 = true; } //Função que reconecta ao servidor MQTT void reconectar() { //Repete até conectar while (!client.connected()) { imprimirSerial(false, "Tentando conectar ao servidor MQTT..."); //Tentativa de conectar. Se o MQTT precisa de autenticação, será chamada a função com autenticação, caso contrário, chama a sem autenticação. bool conectado = strlen(servidor_mqtt_usuario) > 0 ? client.connect("ESP8266Client", servidor_mqtt_usuario, servidor_mqtt_senha) : client.connect("ESP8266Client"); if(conectado) { imprimirSerial(true, "Conectado!"); //Subscreve para monitorar os comandos recebidos client.subscribe(mqtt_topico_sub_lamp_sala, 1); //QoS 1 client.subscribe(mqtt_topico_sub_lamp_cozinha, 1); //QoS 1 } else { imprimirSerial(false, "Falhou ao tentar conectar. Codigo: "); imprimirSerial(false, String(client.state()).c_str()); imprimirSerial(true, " tentando novamente em 5 segundos"); //Aguarda 5 segundos para tentar novamente delay(5000); } } } //Função que verifica qual foi o último status do D5 antes do ESP ser desligado void lerStatusAnteriorD5(){ EEPROM.begin(memoria_alocadaD5); //Aloca o espaco definido na memoria statusAntD5 = EEPROM.read(0); //Le o valor armazenado na EEPROM e passa para a variável "statusAntD5" if(statusAntD5 > 1){ statusAntD5 = 0; //Provavelmente é a primeira leitura da EEPROM, passando o valor padrão para o D5. EEPROM.write(0,statusAntD5); } digitalWrite(D5, statusAntD5); EEPROM.end(); } //Função que verifica qual foi o último status do D0 antes do ESP ser desligado void lerStatusAnteriorD0(){ EEPROM.begin(memoria_alocadaD0); //Aloca o espaco definido na memoria statusAntD0 = EEPROM.read(1); //Le o valor armazenado na EEPROM e passa para a variável "statusAntD0" if(statusAntD0 > 1){ statusAntD0 = 0; //Provavelmente é a primeira leitura da EEPROM, passando o valor padrão para o D0. EEPROM.write(1,statusAntD0); } digitalWrite(D0, statusAntD0); EEPROM.end(); } //Função que grava status do D5 na EEPROM void gravarStatusD5(uint8_t statusD5){ EEPROM.begin(memoria_alocadaD5); EEPROM.write(0, statusD5); EEPROM.end(); } //Função que grava status do D0 na EEPROM void gravarStatusD0(uint8_t statusD0){ EEPROM.begin(memoria_alocadaD0); EEPROM.write(1, statusD0); EEPROM.end(); } //Função que será chamada ao receber mensagem do servidor MQTT do D5 e D0 void retorno(char* topico, byte* mensagem, unsigned int tamanho) { //Convertendo a mensagem recebida para string mensagem[tamanho] = '\0'; String strMensagem = String((char*)mensagem); strMensagem.toLowerCase(); //float f = s.toFloat(); imprimirSerial(false, "Mensagem recebida! Topico: "); imprimirSerial(false, topico); imprimirSerial(false, ". Tamanho: "); imprimirSerial(false, String(tamanho).c_str()); imprimirSerial(false, ". Mensagem: "); imprimirSerial(true, strMensagem); //Executando o comando solicitado imprimirSerial(false, "Status do D5 e D0 antes de processar o comando: "); imprimirSerial(true, String(digitalRead(D5)).c_str()); imprimirSerial(true, String(digitalRead(D0)).c_str()); if(strMensagem == "ligaD5"){ imprimirSerial(true, "Colocando o D5 em stado ALTO..."); digitalWrite(D5, HIGH); gravarStatusD5(HIGH); }else if(strMensagem == "desligaD5"){ imprimirSerial(true, "Colocando o D5 em stado BAIXO..."); digitalWrite(D5, LOW); gravarStatusD5(LOW); }else{ imprimirSerial(true, "Trocando o estado do D5..."); digitalWrite(D5, !digitalRead(D5)); gravarStatusD5(digitalRead(D5)); } if(strMensagem == "ligaD0"){ imprimirSerial(true, "Colocando o D0 em stado ALTO..."); digitalWrite(D0, HIGH); gravarStatusD0(HIGH); }else if(strMensagem == "desligaD0"){ imprimirSerial(true, "Colocando o D0 em stado BAIXO..."); digitalWrite(D0, LOW); gravarStatusD0(LOW); }else{ imprimirSerial(true, "Trocando o estado do D0..."); digitalWrite(D0, !digitalRead(D0)); gravarStatusD0(digitalRead(D0)); } imprimirSerial(false, "Status do D5 e D0 depois de processar o comando: "); imprimirSerial(true, String(digitalRead(D5)).c_str()); imprimirSerial(true, String(digitalRead(D0)).c_str()); } //Função inicial do D5 e D0 (será executado SOMENTE quando ligar o ESP) void setup() { #ifdef DEBUG Serial.begin(115200); #endif imprimirSerial(true, "..."); //Fazendo o D5 e D0 ser de saída, pois ele irá "controlar" algo. pinMode(D5, OUTPUT); pinMode(D0, OUTPUT); //Formatando a memória interna //(descomente a linha abaixo enquanto estiver testando e comente ou apague quando estiver pronto) //SPIFFS.format(); //Iniciando o SPIFSS (SPI Flash File System) imprimirSerial(true, "Iniciando o SPIFSS (SPI Flash File System)"); if (SPIFFS.begin()) { imprimirSerial(true, "Sistema de arquivos SPIFSS montado!"); if (SPIFFS.exists("/config.json")) { //Arquivo de configuração existe e será lido. imprimirSerial(true, "Abrindo o arquivo de configuracao..."); File configFile = SPIFFS.open("/config.json", "r"); if (configFile) { imprimirSerial(true, "Arquivo de configuracao aberto."); size_t size = configFile.size(); //Alocando um buffer para armazenar o conteúdo do arquivo. std::unique_ptr buf(new char[size]); configFile.readBytes(buf.get(), size); DynamicJsonBuffer jsonBuffer; JsonObject& json = jsonBuffer.parseObject(buf.get()); json.printTo(Serial); if (json.success()) { //Copiando as variáveis salvas previamente no aquivo json para a memória do ESP. imprimirSerial(true, "arquivo json analisado."); strcpy(servidor_mqtt, json["servidor_mqtt"]); strcpy(servidor_mqtt_porta, json["servidor_mqtt_porta"]); strcpy(servidor_mqtt_usuario, json["servidor_mqtt_usuario"]); strcpy(servidor_mqtt_senha, json["servidor_mqtt_senha"]); strcpy(mqtt_topico_sub_lamp_sala, json["mqtt_topico_sub_lamp_sala"]); strcpy(mqtt_topico_sub_lamp_cozinha, json["mqtt_topico_sub_lamp_cozinha"]); } else { imprimirSerial(true, "Falha ao ler as configuracoes do arquivo json."); } } } } else { imprimirSerial(true, "Falha ao montar o sistema de arquivos SPIFSS."); } //Fim da leitura do sistema de arquivos SPIFSS //Parâmetros extras para configuração //Depois de conectar, parameter.getValue() vai pegar o valor configurado. //Os campos do WiFiManagerParameter são: id do parâmetro, nome, valor padrão, comprimento WiFiManagerParameter custom_mqtt_server("server", "Servidor MQTT", servidor_mqtt, 40); WiFiManagerParameter custom_mqtt_port("port", "Porta", servidor_mqtt_porta, 6); WiFiManagerParameter custom_mqtt_user("user", "Usuario", servidor_mqtt_usuario, 20); WiFiManagerParameter custom_mqtt_pass("pass", "Senha", servidor_mqtt_senha, 20); WiFiManagerParameter custom_mqtt_topic_sub_lamp_sala("topic_sub", "Topico para subscrever", mqtt_topico_sub_lamp_sala, 30); WiFiManagerParameter custom_mqtt_topic_sub_lamp_cozinha("topic_sub", "Topico para subscrever", mqtt_topico_sub_lamp_cozinha, 30); //Inicialização do WiFiManager. Uma vez iniciado não é necessário mantê-lo em memória. WiFiManager wifiManager; //Definindo a função que informará a necessidade de salvar as configurações do D5 e D0 wifiManager.setSaveConfigCallback(precisaSalvarD5Callback); wifiManager.setSaveConfigCallback(precisaSalvarD0Callback); //Adicionando os parâmetros para conectar ao servidor MQTT wifiManager.addParameter(&custom_mqtt_server); wifiManager.addParameter(&custom_mqtt_port); wifiManager.addParameter(&custom_mqtt_user); wifiManager.addParameter(&custom_mqtt_pass); wifiManager.addParameter(&custom_mqtt_topic_sub_lamp_sala); wifiManager.addParameter(&custom_mqtt_topic_sub_lamp_cozinha); //Busca o ID e senha da rede wifi e tenta conectar. //Caso não consiga conectar ou não exista ID e senha, //cria um access point com o nome "AutoConnectAP" e a senha "senha123" //E entra em loop aguardando a configuração de uma rede WiFi válida. if (!wifiManager.autoConnect("AutoConnectAP", "senha123")) { imprimirSerial(true, "Falha ao conectar. Excedeu o tempo limite para conexao."); delay(3000); //Reinicia o ESP e tenta novamente ou entra em sono profundo (DeepSleep) ESP.reset(); delay(5000); } //Se chegou até aqui é porque conectou na WiFi! imprimirSerial(true, "Conectado!! :)"); //Lendo os parâmetros atualizados strcpy(servidor_mqtt, custom_mqtt_server.getValue()); strcpy(servidor_mqtt_porta, custom_mqtt_port.getValue()); strcpy(servidor_mqtt_usuario, custom_mqtt_user.getValue()); strcpy(servidor_mqtt_senha, custom_mqtt_pass.getValue()); strcpy(mqtt_topico_sub_lamp_sala, custom_mqtt_topic_sub_lamp_sala.getValue()); strcpy(mqtt_topico_sub_lamp_cozinha, custom_mqtt_topic_sub_lamp_cozinha.getValue()); //Salvando os parâmetros informados na tela do D5 web do WiFiManager if (precisaSalvarD5) { imprimirSerial(true, "Salvando as configuracoes"); DynamicJsonBuffer jsonBuffer; JsonObject& json = jsonBuffer.createObject(); json["servidor_mqtt"] = servidor_mqtt; json["servidor_mqtt_porta"] = servidor_mqtt_porta; json["servidor_mqtt_usuario"] = servidor_mqtt_usuario; json["servidor_mqtt_senha"] = servidor_mqtt_senha; json["mqtt_topico_sub_lamp_sala"] = mqtt_topico_sub_lamp_sala; File configFile = SPIFFS.open("/config.json", "w"); if (!configFile) { imprimirSerial(true, "Houve uma falha ao abrir o arquivo de configuracao para incluir/alterar as configuracoes."); } json.printTo(Serial); json.printTo(configFile); configFile.close(); } //Salvando os parâmetros informados na tela do D0 web do WiFiManager if (precisaSalvarD0) { imprimirSerial(true, "Salvando as configuracoes"); DynamicJsonBuffer jsonBuffer; JsonObject& json = jsonBuffer.createObject(); json["servidor_mqtt"] = servidor_mqtt; json["servidor_mqtt_porta"] = servidor_mqtt_porta; json["servidor_mqtt_usuario"] = servidor_mqtt_usuario; json["servidor_mqtt_senha"] = servidor_mqtt_senha; json["mqtt_topico_sub_lamp_cozinha"] = mqtt_topico_sub_lamp_cozinha; File configFile = SPIFFS.open("/config.json", "w"); if (!configFile) { imprimirSerial(true, "Houve uma falha ao abrir o arquivo de configuracao para incluir/alterar as configuracoes."); } json.printTo(Serial); json.printTo(configFile); configFile.close(); } imprimirSerial(false, "IP: "); imprimirSerial(true, WiFi.localIP().toString()); //Informando ao client do PubSub a url do servidor e a porta. int portaInt = atoi(servidor_mqtt_porta); client.setServer(servidor_mqtt, portaInt); client.setCallback(retorno); //Obtendo o status do D5 e D0 antes do ESP ser desligado lerStatusAnteriorD5(); lerStatusAnteriorD0(); } //Função de repetição (será executado INFINITAMENTE até o ESP ser desligado) void loop() { if (!client.connected()) { reconectar(); } client.loop(); }