Automatização de até 8 reles ou lampadas, com acionamento local físico ou digital pela internet

hassio
mqtt

(José Petri de Lima) #1

Bom dia a todos, tudo bem com vocês? Meu nome é José Pétri e estou querendo desenvolver um sistema super robusto e confiável para automatizar a nossa casa. Não só isso, mas também para coletar dados (isso vai ficar para um próximo post).

No caso, até o momento, já montei um sistema com Hassio e ESP8266 que liga e apaga as luzes, que possui funções, que verifica se há ou não energia na rede (se não houver energia a lampada tem de estar apagada então se o botão no mqtt estiver como ON é mudado para OFF) enfim, segue um vídeo abaixo para melhor visualização =]

Então o sistema já esta funcionando e rodando bem. Dá para ser utilizado já, mas desejo melhorar mais. Por exemplo, se não houver internet o ESP8266 não se conecta a nada e não funciona mais. Quero criar uma rede mesh independente e permitir que esta rede conecte-se ao ESP8266. Como estou usando o Raspberry acredito que possa colocar dois adaptadores de rede USB e usar um para receber a internet e outro para enviar o MQTT por outra rede. Assim, se algo acontecer com o roteador a rede fica independente. Não sei, ainda estou amadurecendo a ideia.

Se gostarem do código ou se verem alguma parte que possa ser melhorada, qualquer que seja, por favor, estou ansioso para receber alguma critica ou conselho =]

//---------------------------------------------------------------------------------------------------------- BIBLIOTECAS ----- ||
  #include <ESP8266WiFi.h> //lib do wifi para o ESP8266
  #include <ESP8266mDNS.h>
  #include <WiFiUdp.h>
  #include <ArduinoOTA.h> //lib do ArduinoOTA
  #include <PubSubClient.h>
  #include <ESP8266WebServer.h>
  #include <WiFiManager.h>
  #include <Ticker.h>// Watcdog bibl

//-------------------------------------------------------------------------------------------------------- IP FIXO TELNET -----||
  IPAddress ip(192,168,0,XXX);//no lugar no XXX coloque o IP
  IPAddress geteway(192,168,0,XXX); //no lugar no XXX coloque o IP do router
  IPAddress subnet(255,255,255,0);
  WiFiServer TelnetServer(23);
  WiFiClient Telnet;

  void handleTelnet(){
if(TelnetServer.hasClient()){
  //Cliente esta conectado
  if(!Telnet || !Telnet.connected()){
    if(Telnet) Telnet.stop();
    Telnet = TelnetServer.available();
  }else{
    TelnetServer.available().stop();
  }
}
  }

// -----------------------------------------------------------------------------------------------------------------  MQTT ----||
  #define MQTT_AUTH true  //Ativa a autenticação
  #define MQTT_USERNAME "@@@@@" // Login do MQTT aqui colocamos o login no lugar do @@@
  #define MQTT_PASSWORD "XXXXXXXXX" // Senha do MQTT aqui colocamos a senha do MQTT
  const String HOSTNAME  = "TANTO FAZ"; // Nome do dispositivo, este nome tambem é utilizado para criar o Access Point para configuração
  const char* servidorMQTT = "192.168.0.XXX"; //IP ou DNS do servidor, neste caso do HassIO

// ----------------------------------------------------------------------------------------------------------------- WIFI -----||
  const char* ssid = "@@@@@@@"; //nome da rede
  const char* password = "XXXXXXXXXXXXX"; //senha da rede
  WiFiClient wclient;
  PubSubClient client(servidorMQTT,1883,wclient);

//--------------------------------------------------------------------------------- MULTIPLICADOR DE ENTRADAS ANALOGICAS ----- ||

  int MUXPinS0 = D3;
  int MUXPinS1 = D4;
  int MUXPinS2 = D5;
  int MUXPinS3 = D6;

//------------------------------------------------------------------------------------- MULTIPLICADOR DE SAIDAS DIGITAIS ----- ||
  #define pinSH_CP D0   //Pino Clock
  #define pinST_CP D1  //Pino Latch
  #define pinDS    D2  //Pino Data
  #define qtdeCI   1


//------------------------------------------------------------------------------- VARIAVEIS DOS BOTÕES, RELES E SENSORES ----- ||
  int const quantidaDeBotao = 4;

  int pinLDR = 0; //luz apagada valor baixo, luz acesa valor alto
  int C1 = 0;
  int X = 0;
  unsigned long delayLamp = 0;
  int temporizador = 0;
  String MQTT_STATE_TOPIC = ""; //Topico onde o dispositivo publica (por exemplo o estado da lâmpada ON ou OFF)
  String MQTT_COMMAND_TOPIC = ""; //Topico onde o dispositivo subscreve (por exemplo controlar uma lâmpada)
  const char * mqttState = "";
  const char * mqttSet = "";
  
  struct Rele{ //Structure dos botões (até o limite de hardware)
char nome[20];
int Pino;
bool estadoDaLuz;
bool estado;
int tensao;
int acionamento = 0;
int funcao;
unsigned long Delay = millis();
  };

  struct Rele Botao[4];


//============================================================================================================================
// ------------------------------------------------< VOID SETUP >-------------------------------------------------------------
//============================================================================================================================
void setup(){
Serial.begin(115200);

//--------------------------------------------------------------------------------- MULTIPLICADOR DE ENTRADAS ANALOGICAS ----- ||
  pinMode(MUXPinS0, OUTPUT);
  pinMode(MUXPinS1, OUTPUT);
  pinMode(MUXPinS2, OUTPUT);
  pinMode(MUXPinS3, OUTPUT);
  
//------------------------------------------------------------------------------------- MULTIPLICADOR DE SAIDAS DIGITAIS ----- ||
   pinMode(pinSH_CP, OUTPUT);
   pinMode(pinST_CP, OUTPUT);
   pinMode(pinDS, OUTPUT);

  WiFiManager wifiManager;
  //wifiManager.resetSettings(); //Limpa a configuração anterior do Wi-Fi SSID e Password, procedimento, 1º descomentar a linha, 2º Fazer Upload do código para o ESP e deixar o ESP arrancar, 3º Voltar a comentar a linha e enviar novamente o código para o ESP
  /*define o tempo limite até o portal de configuração ficar novamente inátivo,
   útil para quando alteramos a password do AP*/
  wifiManager.setTimeout(180);
  wifiManager.autoConnect(HOSTNAME.c_str());
  client.setCallback(callback); //Registo da função que vai responder ás mensagens vindos do MQTT

//----------------------------------------------------------------------------------------- INICIANDO SERVIDOR TELNET ---------||
   TelnetServer.begin();
   TelnetServer.setNoDelay(true);





  
  
 
   
   WiFi.mode(WIFI_STA);
   WiFi.begin(ssid, password); 
   
  
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
delay(5000);
ESP.restart();
  }


  // Identificação do 
  ArduinoOTA.setHostname("interruptor");

  // Senha para conexão via OTA
  ArduinoOTA.setPassword("32342415");


  //define o que será executado quando o ArduinoOTA iniciar
  
  ArduinoOTA.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH) {
  type = "sketch";
} else { // U_SPIFFS
  type = "filesystem";
}

  });//startOTA é uma função criada para simplificar o código 

  //define o que será executado quando o ArduinoOTA terminar
  ArduinoOTA.onEnd([]() {
//Serial.println("\nEnd");
  }); //endOTA é uma função criada para simplificar o código 

  //define o que será executado quando o ArduinoOTA estiver gravando
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
//Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });//progressOTA é uma função criada para simplificar o código 

  //define o que será executado quando o ArduinoOTA encontrar um erro
  ArduinoOTA.onError([](ota_error_t error) {
//Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) {
  //Serial.println("Auth Failed");
} else if (error == OTA_BEGIN_ERROR) {
  //Serial.println("Begin Failed");
} else if (error == OTA_CONNECT_ERROR) {
  //Serial.println("Connect Failed");
} else if (error == OTA_RECEIVE_ERROR) {
  //Serial.println("Receive Failed");
} else if (error == OTA_END_ERROR) {
  //Serial.println("End Failed");
}
  });//errorOTA é uma função criada para simplificar o código 
  
  //inicializa ArduinoOTA
ArduinoOTA.begin();


  
  
} 
//============================================================================================================================
// ------------------------------------------------> VOID SETUP <-------------------------------------------------------------
//============================================================================================================================






//--------------------------- LEITURA DOS VALORES ANALOGICOS DA PLACA 16 CANAIS -------------------||
double getAnalog(int MUXyPin) {
  //MUXyPin must be 0 to 15 representing the analog pin you want to read
  //MUXPinS3 to MUXPinS0 are the Arduino pins connected to the selector pins of this board.
  digitalWrite(MUXPinS3, HIGH && (MUXyPin & B00001000));
  digitalWrite(MUXPinS2, HIGH && (MUXyPin & B00000100));
  digitalWrite(MUXPinS1, HIGH && (MUXyPin & B00000010));
  digitalWrite(MUXPinS0, HIGH && (MUXyPin & B00000001));
  return (double)analogRead(A0);
}

String mqttAState(int S){
  switch (S){
      case 0:
        return "casa/sala/central/state"; 
        break;
      case 1:
        return "casa/sala/tv/state";
        break;
      case 2:
        return "casa/sala/led/state";
        break; 
      case 3:
        return "casa/sala/corredor/state";
        break;     
      default  :
        return "default default default default ";     
  }
}

String mqttASet(int S){
  switch (S){
      case 0:
        return "casa/sala/central/set";
        break;
      case 1:
        return "casa/sala/tv/set";
        break;
      case 2:
        return "casa/sala/led/set";
        break;     
      case 3:
        return "casa/sala/corredor/set";
        break;  
      default  :
        return "default default default default ";  
  }
}









void ci74HC595Write(byte pino, bool estado) {
static byte ciBuffer[qtdeCI];

bitWrite(ciBuffer[pino / 8], pino % 8, estado);

digitalWrite(pinST_CP, LOW); //Inicia a Transmissão

digitalWrite(pinDS, LOW);    //Apaga Tudo para Preparar Transmissão
digitalWrite(pinSH_CP, LOW);

for (int nC = qtdeCI-1; nC >= 0; nC--) {
for (int nB = 7; nB >= 0; nB--) {

    digitalWrite(pinSH_CP, LOW);  //Baixa o Clock      
    
    digitalWrite(pinDS,  bitRead(ciBuffer[nC], nB) );     //Escreve o BIT
    
    digitalWrite(pinSH_CP, HIGH); //Eleva o Clock
    digitalWrite(pinDS, LOW);     //Baixa o Data para Previnir Vazamento      
}  
}

digitalWrite(pinST_CP, HIGH);  //Finaliza a Transmissão

}


//--------------------------------------------------------------------------------------- FUNÇÕES MQTT -------------------------
//Chamada de recepção de mensagem 


void callback(char* topic, byte* payload, unsigned int length) { //Padrão de recebimento do MQTT
  //Primeiro recebe o Topico, Mensagem do programa, tamanho da mensagem



  String payloadStr = "";
  for (int i=0; i<length; i++) { //Abre a mensagem recebida de acordo com o tamanho informado.
payloadStr += (char)payload[i];
  }


  
   String topicStr = String(topic);
   /*for (int i=0; i<length; i++) { //Abre a mensagem recebida de acordo com o tamanho informado.
payloadStr += (char)topic[i];
  }
 */
  
  
  
 Serial.println(topicStr);


  for(int i=0;i<quantidaDeBotao;i++){
if(topicStr == mqttAState(i)){
  X=i;
}
if(topicStr == mqttASet(i)){
  X=i;
}
  }

  Serial.println(X);
Botao[X].Pino = X;
MQTT_STATE_TOPIC = mqttAState(X);
MQTT_COMMAND_TOPIC = mqttASet(X);
mqttState = MQTT_STATE_TOPIC.c_str();
mqttSet = MQTT_COMMAND_TOPIC.c_str();


  if(topicStr.equals(mqttSet)){
if(payloadStr.equals("ON")){
  ci74HC595Write(X, LOW);
  Botao[X].estadoDaLuz = true; //controle interno da luz, true acesa, false apagada.
  Botao[X].estado = true; 
  Botao[X].Delay = millis();//Espera para que a aplicação seja aplicada no circuito e ele não reporte um falso positivo para o acionamento do interruptor. 
  client.publish(mqttState,"ON",true);
}else if(payloadStr.equals("OFF")){
  ci74HC595Write(X, HIGH);
  client.publish(mqttState,"OFF",true);
  Botao[X].estadoDaLuz = false; //controle interno da luz, true acesa, false apagada.
  Botao[X].estado = false; 
  Botao[X].Delay = millis();//Espera para que a aplicação seja aplicada no circuito e ele não reporte um falso positivo para o acionamento do interruptor. 
}
  } 
}
 

//Verifica se o estado da ligação está ativa e se não estiver tenta conectar-se
bool checkMqttConnection(){
  
//for(int B=0;B<quantidaDeBotao;B++){





  if (!client.connected()) {
if (MQTT_AUTH ? client.connect(HOSTNAME.c_str(),MQTT_USERNAME, MQTT_PASSWORD) : client.connect(HOSTNAME.c_str())) {
  //SUBSCRIÇÃO DE TOPICOS

  
  for(int i=0;i<quantidaDeBotao;i++){
  MQTT_COMMAND_TOPIC = mqttASet(i);
  mqttSet = MQTT_COMMAND_TOPIC.c_str();
  client.subscribe(mqttSet);
  }
}
  }
  return client.connected();
}


void desligaTudo(){

for(int i = 0; i<quantidaDeBotao; i++){
ci74HC595Write(i, LOW);
MQTT_STATE_TOPIC = mqttAState(i);
mqttState = MQTT_STATE_TOPIC.c_str();
    client.publish(mqttState,"OFF",true);
    Botao[i].estadoDaLuz = false;
    Botao[i].estado = false;
}
}

void ligaTudo(){

for(int i = 0; i<quantidaDeBotao; i++){
ci74HC595Write(i, HIGH);
MQTT_STATE_TOPIC = mqttAState(i);
mqttState = MQTT_STATE_TOPIC.c_str();
    client.publish(mqttState,"ON",true);
    Botao[i].estadoDaLuz = true;
    Botao[i].estado = true;
}
}


//============================================================================================================================
// ------------------------------------------------( VOID LOOP )--------------------------------------------------------------
//============================================================================================================================
void loop(){


//----------------- medidor de tensão-------------------------------------------------------------------

for(int i=0;i<quantidaDeBotao;i++){
  

int estadobutton = getAnalog(i+6);

if(Botao[i].acionamento == 0 && estadobutton>=600){
Botao[i].Delay = millis();
Botao[i].acionamento = 1;
}


if(Botao[i].acionamento == 1 && estadobutton<=400){
  if((millis() - Botao[i].Delay) <= 500){
    Botao[i].estado = !Botao[i].estado;
    Botao[i].acionamento = 0;
    Serial.printf("O botão %d esta no estado %s\nPressionado rapidamenten\n\n",i,Botao[i].estado?"true":"false");
    }
   if((millis() - Botao[i].Delay) >= 500){
      
      
      switch (i){
      case 0:
        ligaTudo();
        break;
      case 1:
        desligaTudo();
        break;
      case 2:
        
        break;     
      case 3:
        
        break;  
      default  :
          ;
  }

    Botao[i].acionamento = 0;
    Serial.printf("O botão %d esta no estado %s\nPressionado devagar\n\n",i,Botao[i].estado?"true":"false");
    }
    
}




}
  
Telnet.printf("Estado da Lampada = %d", Botao[0].tensao);




//---------------------------------------------------------------------------------------
for(int i = 0; i<quantidaDeBotao; i++){

MQTT_STATE_TOPIC = mqttAState(i);
mqttState = MQTT_STATE_TOPIC.c_str();

if(Botao[i].estado == false && Botao[i].estadoDaLuz == true) {
    client.publish(mqttState,"OFF",true);
    Botao[i].estadoDaLuz = false;
    ci74HC595Write(i, LOW);
}
if(Botao[i].estado == true && Botao[i].estadoDaLuz == false){
    client.publish(mqttState,"ON",true);
    Botao[i].estadoDaLuz = true;
    ci74HC595Write(i, HIGH);
}
}


//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++



Telnet.println("");

if(temporizador>0){
  temporizador--;
}






int valorLDR = getAnalog(pinLDR);

if(valorLDR<=400 && C1>= 800){
Telnet.printf("Lampada queimada!!!");
}else{
Telnet.printf("Intensidade da luz: %d", valorLDR);Telnet.println("");Telnet.println("");
}
C1=0;





// -------------------------------------------------------------------------------------------------------- CONEXÃO MQTT -----
  if (WiFi.status() == WL_CONNECTED) {
  if (checkMqttConnection()){
      client.loop();
  }
  }
// ------------------------------------------------------------------------------------------------------ CONEXÃO TELNET -----
handleTelnet();

  ArduinoOTA.handle();


  delay(100);
}

Aqui tenho uma lista de reprodução com o projeto, podem pular para o último video que é o do codigo acima.

Obrigado pela atenção.


(Luís Miguel Andrade) #2

O mesh de wifi só está devidamente implementado ao nível de APs de routing e não end-points devices. Não é preferivel colocar uma ups no AP/Router?!

A solução mais simples para manter a comunicação entre ESPs no caso de falha de comunicação com o host do HA/mqtt é adicionar paralelamente um protocolo descentralizado (tipo knx por ip). Podes assim garantir que caso o servidor de HA ou mqtt estiver indisponível, a alterção dos sensores tenham resultado nos atuadores.


(José Petri de Lima) #3

É bem isso que eu quero, que no caso do HA ou MQTT esteja offline que ao pressionar um botão o efeito seja realizado na lampada. Teria algum tutorial ou documentação sobre isso? Obrigado.


(Nuno Neves) #4

Podes ver na wiki do tasmota o funcionamento do knx ip que está implementado nesse firmware…


(Luís Miguel Andrade) #5

@josepetri

O sistema que apresento na foto está implementado para poder atuar os relés do 4ch, via wifi, por subscrição de um address, emitido por um switch do NodeMCU… o mesmo acontece de forma inversa. O esp01 está a subscrever ambos para simular um terceiro equipamento.
Como referi, este sistema é descentralizado e funciona de forma paralela ao servidor. Contudo, quando a comunicação estiver novamente restabelecida com o sistema central, as alterações de estado vão ficar refletidas no HA/mqtt.


(José Petri de Lima) #6

Muito interessante. Quero montar algo bem nesse estilo. No caso teria o projeto no github ou em um tutorial em vídeo? O Nuno Neves me deu a dica de procurar o código do Tasmota que já esta com o KNX IP implementado. Isso tudo é novidade para mim, mas parece ser bem promissor. Quero me dedicar a isso em breve, logo terminam as provas da faculdade e minhas ferias já estão reservadas para me dedicar a este projeto =]

Quero montar este sistema bem robusto e eficiente e em seguida publicar o hardware e o software para que qualquer um possa faz o download e montar em casa.


(system) fechado #7

Este tópico foi automaticamente fechado 90 dias após a última resposta. Novas respostas não são permitidas.