Today, we’re involved with SIM800L with M5Stack. This is an ESP32 that is already wrapped and encapsulated with a display. And if you don’t know the M5Stack, watch this video: ESP32 M5Stack with DHT22. And today’s example already has an SIM800L embedded in it. So with our assembly, it is possible to turn a lamp or fan on and off through an SMS message. And I can send this remotely, as there is no need for any application. This is a simple and reliable example of automation, and it does not require WiFi or LoRa.
Step 1: Demonstration- ESP32 M5Stack Dev kit
- SIM800L GSM module for M5Stack
- 4-relay module
- Smartphone
- 2 Cell phone chips (with credit)
- 1 Socket extension
- 2 Lamps
- Wires
- Jumpers
Insert a SIM Card into the SIM800L module, as shown.
Step 6: AssemblyM5Stack
https://github.com/m5stack/M5Stack
TinyGSMClient
https://github.com/vshymanskyy/TinyGSM
Arduino Core for ESP32
https://github.com/espressif/arduino-esp32
* The HardwareSerial library is part of the Arduino Core for the ESP32 library
Step 9: M5Stack Code - Declarations and Variables#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;
Step 10: M5Stack Code - Setupvoid 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;
}
Step 11: M5Stack Code - 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);
}
}
Step 12: M5Stack Code - 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)
}
Step 13: M5Stack Code - 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
}
}
Step 14: M5Stack Code - 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;
}
Step 15: M5Stack Code - SerialInit and 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);
}
Step 16: M5Stack Code - 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;
Step 17: M5Stack Code - 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();
}
}
Step 18: M5Stack Code - 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;
}
Step 19: M5Stack Code - 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);
}
}
Step 20: M5Stack Code - 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;
}
Step 21: M5Stack Code - 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");
}
}
}
Step 22: M5Stack Code - 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;
}
Step 23: M5Stack Code - 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;
}
Step 24: M5Stack Code - 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;
}
Step 25:
// 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;
}
Step 26: M5Stack Code - 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);
}
Step 27: M5Stack Code - 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);
}
Step 28: M5Stack Code - 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;
}
Step 29: Files
Download the files
Comments