SIM800L com M5Stack, que é um
ESP32 já com invólucro, encapsulado com display e, se você não conhece o
M5Stack, assiste esse vídeo: ESP32 M5Stack com DHT22. Portanto, hoje, vamos
usar esse “cara” que eu gosto demais, o qual tem ainda um SIM800L embutido
nele. Então, com a nossa montagem é possível acender e apagar lâmpada, além de
um ventilador, isso através de mensagem SMS, que posso enviar remotamente, sem
necessidade de aplicativo nenhum. Esta aí um exemplo simples e confiável de
automação, que não exige uso de WiFi ou LoRa.
Demonstração
Recursos usados
- ESP32 M5Stack Dev kit
- Módulo GSM SIM800L para M5Stack
- Módulo de 4 relés
- Smartphone
- 2 Chips de celular (com crédito)
- 1 Extensão de tomadas
- 2 Lâmpadas
- Fios
- Jumpers
M5Stack Pinout
M5Stack Pinout (Externo)
SIM800L Chip
Insira um SIM Card no módulo
SIM800L conforme a imagem
Montagem
Código
Diagrama
Instalação de Bibliotecas
M5Stack
TinyGSMClient
*A biblioteca HardwareSerial é
parte da biblioteca Arduino Core for ESP32
Código M5Stack
Declarações e variáveis
#include <M5Stack.h> // Biblioteca do M5Stack #include <HardwareSerial.h> //Biblioteca para comunicação Serial #define TINY_GSM_MODEM_SIM800 // Definição do tipo de modem usado (SIM800L) #include <TinyGsmClient.h> // Biblioteca GSM // Variáveis do display M5Stack #define WDT_SCR 320 // Tamanho #define HGT_SCR 240 // Altura #define WDT_SCR2 160 // Metade do tamanho #define HGT_SCR2 120 // Metade da altura // Celulares com permissões para enviar SMS const int sizeListNumbers = 2; const String numbers[sizeListNumbers] = {"+5518999999999", "+5518999999999"}; // Objeto de comunicação serial do SIM800L HardwareSerial SerialGSM(2); // Velocidade da serial do SIM800L (A velocidade do monitor serial é de 115200) const int BAUD_RATE = 9600; // Pinos dos relés const int relayPin1 = 21, relayPin2 = 22, relayPin3 = 19; // Objeto com as funções GSM TinyGsm modemGSM(SerialGSM); // Altura da fonte (FreeSerif24pt7b) 24 + 4 de espaçamento const int fontHeigth = 28; // Assinatura da função "M5StackButtonsRead" (para evitar erros de compilação) void M5StackButtonsRead(void *); // Rodaremos duas tasks, onde ambas utilizam os pinos dos relés // Essa flag indica se os pinos dos relés estão ocupados e evita que duas operações sejam feitas ao mesmo tempo bool pinsBusy = false; // Variáveis de referência contagem de tempo (millis), usados na nossa função de timeout long prevMillisStartScreen, prevMillisReadSerial; // Flags que indicam o início de contagem de tempo (millis), usados na nossa função de timeout bool flagStartScreen = false, flagReadSerial = false;
Setup
void setup() { // Iniciamos o Objeto, desabilitando o SD, que não será usado M5.begin(true,false,true); //LCD, SD, Serial // Exibimos os textos "Relay 1", "Relay 2" e "Relay 3" // Referentes aos botões do M5Stack showLabelButtons(); // Setamos os relés (output) e criamos uma task de leitura dos botões inputOutputInit(); // Iniciamos o monitor serial e modemGSM com suas respectivas velocidades serialInit(); // Iniciamos o display displayInit(); // Reiniciamos o modem, informando que se for encontrado algum problema deve-se reiniciar o ESP modemRestart(true); // Exibe a tela inicial com a mensagem "Waiting for messages" showStartScreen(); // Setamos as flags de contagem de tempo como true flagStartScreen = flagReadSerial = true; }
Loop
// OBS: Para atualizar os componentes do M5Stack é necessário chamar a função M5.update(); // Essa função já é chamada na task (core 0) 'M5StackButtonsRead', então não é necessário chamá-la novamente no loop void loop() { // A cada 300ms verificamos se existem novos SMS if(modemGSM.getSimStatus() == SIM_READY && timeout(300, &prevMillisReadSerial, &flagReadSerial)) { // Printamos um ponto (debug) indicando a frequencia em que a função "verifySMSMessages" é chamada Serial.print("."); verifySMSMessages(); } // A cada 5 segundos atualizamos o display com a tela inicial if(timeout(5000, &prevMillisStartScreen, &flagStartScreen)) { showStartScreen(); // Se o status do SIM não for "Ready" (pronto), reiniciamos o modem if(modemGSM.getSimStatus() != SIM_READY) modemRestart(false); } }
inputOutputInit
// Iniciamos os pinos e a task que ficará varrendo os botões do M5Stack // Os botões do M5Stack não precisam ser setados no pinMode void inputOutputInit() { pinMode(relayPin1, OUTPUT); pinMode(relayPin2, OUTPUT); pinMode(relayPin3, OUTPUT); // Digital write com flag (Os relés possuem lógica inversa) pinsControl(relayPin1, HIGH); pinsControl(relayPin2, HIGH); pinsControl(relayPin3, HIGH); // Iniciamos uma task que irá ler os botões xTaskCreatePinnedToCore( M5StackButtonsRead, //Função que será executada "M5StackButtonsRead", //Nome da tarefa 10000, //Tamanho da pilha NULL, //Parâmetro da tarefa (no caso não usamos) 2, //Prioridade da tarefa NULL, //Caso queira manter uma referência para a tarefa que vai ser criada (no caso não precisamos) 0); //Número do core que será executada a tarefa (usamos o core 0 para o loop ficar livre com o core 1) }
M5StackButtonsRead
// Task que lê o botão a cada 10ms e controla os relés void M5StackButtonsRead(void *p) { TickType_t taskDelay = 10 / portTICK_PERIOD_MS; //IMPORTANTE: SEMPRE DEIXAR UM LOOP COM UM DELAY PARA ALIMENTAR WATCHDOG while(true) { // Se o primeiro botão "Relay 1" foi pressionado, modamos o estado do relé 1 if(M5.BtnA.wasPressed()) pinsControl(relayPin1, !digitalRead(relayPin1)); // Se o segundo botão "Relay 2" foi pressionado, modamos o estado do relé 2 if(M5.BtnB.wasPressed()) pinsControl(relayPin2, !digitalRead(relayPin2)); // Se o terceiro botão "Relay 3" foi pressionado, modamos o estado do relé 3 if(M5.BtnC.wasPressed()) pinsControl(relayPin3, !digitalRead(relayPin3)); // Executamos um delay de 10ms, os delays executado nas xTasks são diferentes vTaskDelay(taskDelay); // Atualizamos o objeto M5 referente ao M5Stack M5.update(); //IMPORTANTE: SEMPRE DEIXAR UM DELAY PARA ALIMENTAR WATCHDOG } }
pinsControl
// Função que verifica se o pino do relé passado por parâmetro está ocupado // E evita que duas operações (uma de cada task) sejam feitas ao mesmo tempo void pinsControl(int pin, int val) { // Enquanto a flag estiver "true", não faz nada while(pinsBusy); // Seta a flag como true pinsBusy = true; // Executa o digitalWrite digitalWrite(pin, val); // Logo em seguida seta a flag como false pinsBusy = false; }
serialInit e displayInit
// Inicia o monitor serial e a serial do modem GSM void serialInit() { Serial.begin(115200); SerialGSM.begin(BAUD_RATE); TinyGsmAutoBaud(SerialGSM); } // Configura o display com as cores de fundo, de texto e a fonte do texto void displayInit() { // Seta a tela de fundo como preta M5.Lcd.fillScreen(BLACK); // Seta a cor do texto como branca M5.Lcd.setTextColor(WHITE); // Seta a fonte do texto M5.Lcd.setFreeFont(&FreeSerif24pt7b); }
modemRestart
// Reiniciamos o modem void modemRestart(bool restartESP) { // Reiniciamos o modem if(!gsmRestart(restartESP)) return; // Aguardamos a mensagem SMS Ready e Call Ready enviada pelo SIM800L if(!waitSMSCallReady(restartESP)) return; // Conectamos na rede gsm if(!waitNetwork(restartESP)) return;
verifySMSMessages
// Verificamos se existem mensagens não lidas void verifySMSMessages() { // Recebemos todas as mensagens não lidas no buffer "msg" String msg = listSMSUnRead(); // Se existe pelo menos uma mensagem if(!msg.equals("")) { // Chamamos a função que separa as mensagems e executa uma ação de acordo com o texto de cada mensagem splitMsg(msg); // Setamos a variável de referencia de contagem de tempo com o tempo atual (millis) para que ela seja mostrada só após o timeout (5 segundos) prevMillisStartScreen = millis(); } }
listSMSUnread
// Enviamos um comando AT pedindo que o modem nos retorne uma lista de SMS ainda não lidos String listSMSUnRead() { String msg = ""; // Enviamos um comando AT modemGSM.sendAT("+CMGL=\"REC UNREAD\"\r\n"); // Aguardamos uma resposta em até 5 segundos modemGSM.waitResponse(5000, msg); // Se não recebemos a resposta, aborta função if(msg.equals("")) return ""; //Serial.println("Response:["+msg+"]"); // Se a resposta recebida for uma mensagem de Erro //Exemplo de mensagem: /* AT+CMGL="REC UNREAD" ERROR */ if(msg.indexOf("AT+CMGL=\"REC UNREAD\"")>=0) { // Exibimos na serial Serial.println(msg); // Reiniciamos o modem // false = não reiniciará o ESP caso ocorra uma falha modemRestart(false); // Retornamos vazio return ""; } // Se a mensagem possuir "OK", "SMS Ready" ou "Call Ready", abortamos a função pois não é o SMS em si if((msg.indexOf("OK")<=4 && msg.indexOf("OK")>=0) || (msg.indexOf("Call Ready")<=4 && msg.indexOf("Call Ready")>=0) || (msg.indexOf("SMS Ready")<=4 && msg.indexOf("SMS Ready")>=0)) return ""; // Exibimos a mensagem na serial Serial.println(msg); // Retornamos a String msg return msg; }
splitMsg
// A função "splitMsg" percorre a String "fullMsg" com as mensagens não lidas (Exemplo abaixo) e obtém os respectivos nºs e texto das mensagens /*+CMGL: 1,"REC READ","+5518999999999","","18/12/07,08:57:56-08" Relay 1 on +CMGL: 2,"REC UNREAD","+5518999999999","","18/12/07,08:57:59-08" Relay 2 on OK*/ void splitMsg(String fullMsg) { String msg, number, aux; // Exibe na serial (debug) Serial.println("Full msg:["+fullMsg+"]"); // Para retirarmos os \n do início do buffer, executamos este primeiro while // Enquanto o início da mensagem não for "+CMGL", pulamos uma linha while(!fullMsg.startsWith("+CMGL") && fullMsg.indexOf("\n")>=0) fullMsg = fullMsg.substring(fullMsg.indexOf("\n")+1); // Enquanto o início da mensagem não for "+CMGL", significa que é uma mensagem válida while(fullMsg.startsWith("+CMGL")) { // Obtém a pos da segunda vírgula int pos2ndComma = fullMsg.indexOf(",",fullMsg.indexOf(",")+1); // Obtém substring após a segunda vírgula aux = fullMsg.substring(pos2ndComma+1); // Obtém número que inicia após a primeira aspas e termina antes da segunda aspas number = aux.substring(1, aux.indexOf(",")-1); // Exibe o número de celular que nos enviou o SMS no monitor serial entre colchetes Serial.println("Number:["+number+"]"); // Pula a primeira linha fullMsg = aux.substring(aux.indexOf("\n")+1); // Obtém substring até o primeiro \n msg = fullMsg.substring(0, fullMsg.indexOf("\n")-1); // Exibe o texto da mensagem no monitor serial entre colchetes Serial.println("Msg:["+msg+"]"); // Se o número for válido if(numberIsValid(number)) { // Exibimos no display que um novo SMS chegou showDisplay("A new SMS Arrived!\n", true); showDisplay("\""+msg+"\"", false); // Chamamos a função "newSMS" que executará o comando de acordo com o texto guardado em "msg" newSMS(number, msg); } else { // Exibimos no display que o nº é inválido showDisplay("New SMS...\nInvalid number\n", true); Serial.println("New SMS...\nInvalid number"); } // Pulamos duas linhas para obter a próxima mensagem fullMsg = fullMsg.substring(fullMsg.indexOf("\n", fullMsg.indexOf("\n")+1)+1); } }
numberIsValid
// Verifica se o número é algum dos declarados pelo programa bool numberIsValid(String number) { // Percorre vetor com os números e verifica se é a String é igual for(int i = 0; i < sizeListNumbers; i++) if(numbers[i].equals(number)) return true; return false; }
NewSMS
// Executa uma ação de acordo com a variavel 'msg' void newSMS(String number, String msg) { // "pinsControl" é uma função que é usada pela task core 0 (botão) e também pela task core 1 (loop) e evita que as duas acessem os pinos ao mesmo tempo if(msg.equalsIgnoreCase("relay 1 on")) { // Os reles possuem lógica inversa pinsControl(relayPin1, LOW); } else if(msg.equalsIgnoreCase("relay 1 off")) { pinsControl(relayPin1, HIGH); } else if(msg.equalsIgnoreCase("relay 2 on")) { pinsControl(relayPin2, LOW); } else if(msg.equalsIgnoreCase("relay 2 off")) { pinsControl(relayPin2, HIGH); } else if(msg.equalsIgnoreCase("relay 3 on")) { pinsControl(relayPin3, LOW); } else if(msg.equalsIgnoreCase("relay 3 off")) { pinsControl(relayPin3, HIGH); } else if(msg.equalsIgnoreCase("relays off")) { pinsControl(relayPin1, HIGH); pinsControl(relayPin2, HIGH); pinsControl(relayPin3, HIGH); } else if(msg.equalsIgnoreCase("relays on")) { pinsControl(relayPin1, LOW); pinsControl(relayPin2, LOW); pinsControl(relayPin3, LOW); } else // Faz com que o modem efetue uma ligação para o número que enviou o SMS if(msg.equalsIgnoreCase("call me")) { showDisplay("Calling...\n", true); if(modemGSM.callNumber(number)) { showDisplay("OK\n", false); Serial.println("OK\n"); } else { showDisplay("Failed\n", false); Serial.println("Failed\n"); } modemGSM.callHangup(); } else // Se recebermos um SMS "status", o modem enviará uma resposta SMS com os status dos relés if(msg.equalsIgnoreCase("status")) { String status = "Relay 1: "; // Obtém os status do relé 1 if(digitalRead(relayPin1) == LOW) status+="ON\n"; else status+="OFF\n"; status += "Relay 2: "; // Obtém os status do relé 2 if(digitalRead(relayPin2) == LOW) status+="ON\n"; else status+="OFF\n"; status += "Relay 3: "; // Obtém os status do relé 3 if(digitalRead(relayPin3) == LOW) status+="ON"; else status+="OFF"; // Exibições display e monitor serial showDisplay("Sending SMS...\n",true); Serial.println("Sending SMS..."); // Envia SMS if(modemGSM.sendSMS(number,status)) { showDisplay("OK", false); Serial.println("OK"); } else { showDisplay("Failed", false); Serial.println("Failed"); } } else // Se recebermos um SMS "delete all", o modem excluirá todos os SMS (lidos e não lidos) e enviará uma resposta SMS se a exclusão foi ou não possível if(msg.equalsIgnoreCase("delete all")) { String respMsg; // Deleta todos os SMS if(deleteALLSMS()) respMsg = "The messages have been deleted"; else respMsg = "Failed to delete messages"; // Exibições display e monitor serial showDisplay("Sending SMS...\n",true); Serial.println("Sending SMS..."); // Envia SMS if(modemGSM.sendSMS(number, respMsg)) { showDisplay("OK", false); Serial.println("OK"); } else { showDisplay("Failed", false); Serial.println("Failed"); } } }
deleteALLSMS
// Função que deleta todos os SMS bool deleteALLSMS() { String resp = ""; // Enviamos um comando AT modemGSM.sendAT("+CMGD=1,4\r\n"); // Aguardamos uma resposta em até 3 segundos modemGSM.waitResponse(3000, resp); // Se a resposta conter um "OK" retornamos "true", se não retornamos "false" return resp.indexOf("OK")>=0; }
GsmRestart
// Reinicia o modem, exibindo no monitor serial e no display o sucesso ou falha bool gsmRestart(bool restart) { // Exibições display e monitor serial showDisplay("Restarting modem...\n", true); Serial.println("Restarting modem..."); // Se foi possível reiniciar o modem if(modemGSM.restart()) { // Exibições display e monitor serial (sucesso) M5.Lcd.setCursor(0,0); showDisplay("Modem ", true); M5.Lcd.setTextColor(GREEN); showDisplay("OK\n",false); M5.Lcd.setTextColor(WHITE); Serial.println("Modem ok"); return true; }
WaitSMSCallReady
// Aguarda que a mensagem "SMS Ready" seja recebida do modem em até 10 segundos, exibindo no monitor serial e no display o sucesso ou falha bool waitSMSCallReady(bool setup) { // Exibições display e monitor serial Serial.println("Waiting SMS/Call"); showDisplay("Waiting SMS/Call ", false); String msg = ""; long prevMillis = millis(); // Em até 10 segundos aguardamos a mensagem "SMS Ready" while(prevMillis + 10000 > millis() && msg.indexOf("SMS Ready")<0 a="" erialgsm.available="" foi="" if="" mensagem="" msg.indexof="" msg="SerialGSM.readString();" ready="" recebida="" se="">=0) { // Exibições display e monitor serial (sucesso) Serial.println(msg); M5.Lcd.setTextColor(GREEN); showDisplay("OK\n", false); M5.Lcd.setTextColor(WHITE); return true; } // Se a mensagem não foi recebida // Exibições display e monitor serial (falha) Serial.println("SMS/Call init failed"); M5.Lcd.setTextColor(RED); showDisplay("Failed\n", false); M5.Lcd.setTextColor(WHITE); // Se estamos ainda no setup, aguardamos 5 segundos e reiniciamos o ESP if(setup) { delay(3000); ESP.restart(); } return false; } 0>
SetSMSTextMode
// Configuramos a mensagem SMS para modo texto no modem GSM bool setSMSTextMode(bool restart) { String resp = ""; // Enviamos um comando AT modemGSM.sendAT("+CMGF=1\r\n"); // Aguardamos uma responsta em até 3 segundos modemGSM.waitResponse(3000, resp); // Exibição display showDisplay("SMS Text mode ", false); // Se a resposta conter um "OK" if(resp.indexOf("OK")>=0) { // Exibimos no display (sucesso) M5.Lcd.setTextColor(GREEN); showDisplay("OK\n", false); M5.Lcd.setTextColor(WHITE); Serial.println("SMS Text mode ok"); return true; } // Se a resposta não conter um "OK" // Exibimos no display (falha) M5.Lcd.setTextColor(RED); showDisplay("Fail\n", false); M5.Lcd.setTextColor(WHITE); Serial.println("SMS Text mode failed"); // Se a flag "restart" estiver como "true", aguardamos 5 segundos e reiniciamos o ESP if(restart) { delay(5000); ESP.restart(); } return false; }
showStartScreen
// Exibimos a mensagem "Waiting for messages" com uma linha grifando o texto void showStartScreen() { showDisplay("", true); M5.Lcd.fillRect(10, HGT_SCR2, WDT_SCR-20, 1, TFT_WHITE); M5.Lcd.setCursor(5, HGT_SCR2-10); showDisplay("Waiting for messages", false); }
showLabelButtons
// Exibe as labels "Relay 1", "Relay 2" e "Relay 3" void showLabelButtons() { String msg = "Relay 1"; int spacing = 5; M5.Lcd.setFreeFont(&FreeSerif12pt7b); M5.Lcd.setCursor((WDT_SCR2/2 ) - (msg.length()*12 / 2) - spacing,HGT_SCR-20); M5.Lcd.print(msg); msg = "Relay 2"; M5.Lcd.setCursor(WDT_SCR2 - (msg.length()*12 / 2) + spacing*2, HGT_SCR-20); M5.Lcd.print(msg); msg = "Relay 3"; M5.Lcd.setCursor(WDT_SCR2 + (WDT_SCR2/3) + spacing, HGT_SCR-20); M5.Lcd.print(msg); }
timeout
// Função que compara se o tempo foi atingido, sem que 'congele' a execução do loop bool timeout(const int DELAY, long *previousMillis, bool *flag) { if(*flag) { *previousMillis = millis(); *flag = false; } if((*previousMillis + DELAY) < millis()) { *flag = true; return true; } return false; }
2 Comentários
Cara, o conteúdo é muito bom! Estou fazendo engenharia de computação e teu conteúdo me deixou muito empolgado por te relação com o meu curso. Valeu
ResponderExcluirtem como utilizar o speaker como viva voz?
ResponderExcluir