Campainha lora + MQTT com notificação de correio + telegram

Quero partilhar um projeto que estive a desenvolver nestas ultimas semanas que consiste numa campainha sem fios e que reporta para um esp32 com MQTT com um módulo lora RFM95.

Material que usei:

1- Arduino mini pro 3.3v. Tem de ser a versão de 3.3v 8Mhz para não danificar o modulo lora.
1 - Xiao Esp32c3
2 - Modulos Lora RFM95 - 868Mhz.
1 - Pcb para a placa que vai ser de emissor.
1 - Controlador de carga para uma pilha de litio de 3.7v.
1 - Conversor DC-DC para 3.3v para alimentar o arduino.
1 - Reed switch

A alimentação do arduino vai ser feita através de um painel solar de 5v e de uma pilha de litio de 3.7v. Deixo abaixo uma imagem para perceberem a ideia.

Depois de ter encomendado o pcb para o arduino é que descobri que também existia o pcb para o esp32c3. Já fiz a encomenda desse pcb e assim que ele chegar atualizo este post.

1- Pcb para o receptor ESP32C3 .

O código que vou partilhar abaixo não foi testado com o PCB do ESP32C3.

Pinout usado para ligar o ESP32C3 ao modulo lora RFM95:

RFM - XIAO ESP32C3

DIO0 - GPIO2
RST - GPIO3
NSS - GPIO7
SCK - SCK
MOSI - MOSI
MISO - MISO
3.3v - 3.3v
GND - GND

O buzzer/altifalante liga ao GPIO4 e ao GND do esp.

Arduino:

O botão da campainha liga ao GPIO3 e ao GND do arduino.
O reed switch liga ao GPIO4 e ao GND do arduino.

Código para o arduino mini pro:

Devem alterar a linha de Identificador único tanto no código do emissor como no código do receptor.
Ele é utilizado para assegurar que apenas dispositivos emparelhados comuniquem entre si,
garantindo a correta transmissão de mensagens.

Update ao codigo do emissor - 18/06/2024
Acrescentei um led e um besouro ao emissor e de forma a poupar bateria, o reed switch funciona com um botão. Assim sóé enviado a notificação de correio quando o reed switch está aberto.

#include <SPI.h>
#include <LoRa.h>

// Pinos configurados conforme a breakout board DIYcon v2.03
#define SS 10
#define RST 9
#define DIO0 2

// Identificador único
const String identificador = "campainha1";

// Pinos para o botão da campainha, LED, buzzer e botão de correio
#define BUTTON_PIN 3
#define MAIL_BUTTON_PIN 4
#define LED_PIN 5
#define BUZZER_PIN 6

void setup() {
  Serial.begin(9600);
  while (!Serial);

  // Inicializa os pinos conforme a breakout board
  LoRa.setPins(SS, RST, DIO0);

  Serial.println("LoRa Transmitter");

  if (!LoRa.begin(868E6)) {
    Serial.println("Starting LoRa failed!");
    while (1);
  }

  // Configura os pinos dos botões como entrada com pull-up
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(MAIL_BUTTON_PIN, INPUT_PULLUP);

  // Configura os pinos do LED e do buzzer como saída
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);

  // Desliga o LED e o buzzer inicialmente
  digitalWrite(LED_PIN, LOW);
  digitalWrite(BUZZER_PIN, LOW);
}

void loop() {
  // Verifica se o botão da campainha foi pressionado
  if (digitalRead(BUTTON_PIN) == LOW) {
    Serial.println("Button pressed");

    // Acende o LED
    digitalWrite(LED_PIN, HIGH);

    // Mantém o LED ligado por 2 segundos e o buzzer apitando enquanto o botão estiver pressionado
    unsigned long startTime = millis();
    while (millis() - startTime < 2000) {
      if (digitalRead(BUTTON_PIN) == LOW) {
        tone(BUZZER_PIN, 1000); // Frequência de 1000 Hz
      } else {
        noTone(BUZZER_PIN);
      }

      sendMessage(identificador + "Buzzer");
      delay(100); // Pequeno atraso para evitar saturação da rede
    }

    // Assegura que o buzzer está desligado
    noTone(BUZZER_PIN);

    // Desliga o LED após 2 segundos
    digitalWrite(LED_PIN, LOW);

    // Debounce delay para evitar múltiplas detecções
    delay(500);
  }

  // Verifica se o botão de correio foi pressionado
  if (digitalRead(MAIL_BUTTON_PIN) == LOW) {
    Serial.println("Mail button pressed");
    sendMessage(identificador + "Mail");
    delay(500); // Debounce delay para evitar múltiplas detecções
  }

  delay(100);
}

void sendMessage(String message) {
  Serial.print("Sending: ");
  Serial.println(message);

  LoRa.beginPacket();
  LoRa.print(message);
  LoRa.endPacket();
}

Código para o esp32c3 com MQTT ( versão campainha e caixa de correio) :
Nota: O nomes e senhas partilhados nos códigos foram inventados de forma a apresentar o código como ele deve ficar após ser editado com os vossos dados.

const char* telegramBotToken = "1125383575:AAGl22YyQtxyzpauvufYlMLiENbVAEudw"; // Substitua pelo token do seu bot (
const char* telegramChatID = "-419345390"; // Substitua pelo chat ID do seu bot 

Código para o esp32c3 com MQTT ( versão campainha, caixa de correio e notificação via telegram que tem correio) :
Nota: O nomes e senhas partilhados nos códigos foram inventados de forma a apresentar o código como ele deve ficar após ser editado com os vossos dados.

Update 09/08/2024: O esp emite um beep assim que se liga ao wifi e emite outro beep quando se liga ao servidor mqtt.

#include <LoRa.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <UniversalTelegramBot.h>
#include <WiFiClientSecure.h>

// Pinos
const int buzzerPin = 4; // Pino do buzzer
const int loraSS = 7;
const int loraRST = 3;
const int loraDI0 = 2;

// Identificador único esperado
const String identificador = "campainha1";

// WiFi
const char* ssid = "Vodafone-FUB0Y";
const char* password = "A73737rocas70d0";

// MQTT
const char* mqtt_server = "192.168.1.xxx";
const int mqtt_port = 1883;
const char* mqtt_command_topic = "cmnd/campainha/POWER";
const char* mqtt_state_topic = "stat/campainha/POWER";
const char* mqtt_mail_topic = "stat/correio/POWER";
const char* mqtt_user = "userxyx";
const char* mqtt_password = "passwxyz";
const char* mqtt_client_name = "campainha"; // Nome do cliente MQTT

// Telegram
const char* telegramBotToken = "1125383575:AAGl22YyQtxyzpauvufYlMLiENbVAEudw"; // Substitua pelo token do seu bot (
const char* telegramChatID = "-419345390"; // Substitua pelo chat ID do seu bot 

WiFiClient espClient;
WiFiClientSecure secured_client;
PubSubClient client(espClient);
UniversalTelegramBot bot(telegramBotToken, secured_client);

// Flags para controlar o envio de mensagens MQTT e Telegram
volatile bool buzzerEvent = false;
volatile bool mailEvent = false;
volatile bool newMailReceived = false;  // Flag para verificar nova mensagem de correio

void setup() {
  pinMode(buzzerPin, OUTPUT);
  Serial.begin(115200);
  LoRa.setPins(loraSS, loraRST, loraDI0);

  // Inicializa o LoRa
  if (!LoRa.begin(868E6)) {  // Frequência ajustada para 868 MHz
    Serial.println("Erro LoRa");
    while (1);
  }
  Serial.println("LoRa inicializado");

  // Conecta-se à rede Wi-Fi
  Serial.println();
  Serial.println();
  Serial.print("Conectando à rede ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("Conectado à rede WiFi");
  Serial.println("Endereço IP: ");
  Serial.println(WiFi.localIP());

  // Configura o servidor MQTT
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);

  // Configura o cliente seguro para o Telegram
  secured_client.setCACert(TELEGRAM_CERTIFICATE_ROOT); // Certificado root para o Telegram

  // Tenta se conectar ao servidor MQTT
  reconnect();

  Serial.println("Campainha");
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  if (buzzerEvent) {
    if (client.publish(mqtt_state_topic, "ON")) {
      Serial.println("Mensagem MQTT 'ON' enviada com sucesso");
      buzzerEvent = false;
      delay(3000);  // O buzzer toca por 3 segundos
      client.publish(mqtt_state_topic, "OFF");
      Serial.println("Campainha");
    } else {
      Serial.println("Falha ao enviar mensagem MQTT 'ON'");
    }
  }

  if (mailEvent && newMailReceived) {
    if (client.publish(mqtt_mail_topic, "ON")) {
      Serial.println("Mensagem MQTT 'ON' para correio enviada com sucesso");
      mailEvent = false;
      newMailReceived = false;
      delay(5000); // Mostra a mensagem por 5 segundos
      client.publish(mqtt_mail_topic, "OFF");
      Serial.println("Correio");

      // Envia mensagem pelo Telegram
      String message = "Você tem correio!";
      if (bot.sendMessage(telegramChatID, message, "")) {
        Serial.println("Mensagem Telegram enviada com sucesso");
      } else {
        Serial.println("Falha ao enviar mensagem Telegram");
      }
    } else {
      Serial.println("Falha ao enviar mensagem MQTT 'ON' para correio");
    }
  }

  int packetSize = LoRa.parsePacket();
  if (packetSize) {
    String incoming = "";
    while (LoRa.available()) {
      incoming += (char)LoRa.read();
    }
    Serial.print("Mensagem LoRa recebida: ");
    Serial.println(incoming);

    // Verificar se a mensagem recebida contém o identificador correto
    if (incoming.startsWith(identificador)) {
      // Verificar se a mensagem recebida é um sinal para tocar a campainha
      if (incoming.endsWith("Buzzer")) {
        playRedmiMelody();
        buzzerEvent = true;
      }
      // Verificar se a mensagem recebida é um sinal de correio chegou
      if (incoming.endsWith("Mail")) {
        Serial.println("Correio Chegou!");
        mailEvent = true;
        newMailReceived = true;  // Setar flag de nova mensagem de correio
      }
    }
  }
}

void reconnect() {
  // Loop até estar conectado ao WiFi
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  // Conectar ao servidor MQTT
  while (!client.connected()) {
    Serial.print("Conectando ao servidor MQTT...");
    if (client.connect(mqtt_client_name, mqtt_user, mqtt_password)) {
      Serial.println("Conectado");
      // Subscrever ao tópico de comando
      client.subscribe(mqtt_command_topic);
    } else {
      Serial.print("Falha ao conectar, rc=");
      Serial.print(client.state());
      Serial.println(" Tentando novamente em 5 segundos");
      delay(5000);
    }
  }
}

void callback(char* topic, byte* payload, unsigned int length) {
  String message;
  for (unsigned int i = 0; i < length; i++) {
    message += (char)payload[i];
  }
  message.trim();
  Serial.print("Mensagem recebida [");
  Serial.print(topic);
  Serial.print("]: ");
  Serial.println(message);

  if (String(topic) == mqtt_command_topic) {
    if (message.equalsIgnoreCase("on")) {
      playRedmiMelody();
      client.publish(mqtt_state_topic, "ON");
    } else if (message.equalsIgnoreCase("off")) {
      noTone(buzzerPin);
      Serial.println("Campainha");
      client.publish(mqtt_state_topic, "OFF");
    }
  }
}

void playRedmiMelody() {
  // Notas e durações para um toque mais complexo
  int melody[] = {
    659, 659, 523, 659, 784, 392,
    523, 392, 330, 440, 494, 466, 440, 392, 659, 784, 880,
    698, 784, 659, 523, 587, 494
  };
  int noteDurations[] = {
    125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125,
    125, 125, 125, 125, 125, 125, 125, 125, 125, 125
  };

  Serial.println("A Tocar...");
  for (int i = 0; i < sizeof(melody) / sizeof(int); i++) {
    tone(buzzerPin, melody[i], noteDurations[i]);
    delay(noteDurations[i] * 1.30);
    noTone(buzzerPin);
  }
}

Partilho tambem a automação que fiz para notificar pelo HA que temos correio.
configuration.yaml

mqtt:
  binary_sensor:
    - name: "Campainha"
      state_topic: "stat/campainha/POWER"
      device_class: sound
      qos: 0
      unique_id: "campainha_sensor"

    - name: "Correio"
      state_topic: "stat/correio/POWER"
      device_class: sound  
      qos: 0
      unique_id: "campainha_correio" 

campainha.yaml

automation:
  - alias: Notificação de Campainha
    trigger:
      platform: state
      entity_id: binary_sensor.campainha
      to: 'on'
    action:
      service: notify.notify
      data:
        message: "A campainha está a tocar!"

  - alias: Notificação de Correio
    trigger:
      platform: state
      entity_id: binary_sensor.correio
      to: 'on'
    action:
      service: notify.notify
      data:
        message: "Chegou correio"

Automação em NODE RED :

Alexa avisa que estão a tocar à campainha.

[{"id":"4322e1fcbbf89b87","type":"api-call-service","z":"af740ccdec97ba89","name":"","server":"79fd2272.3f2bdc","version":5,"debugenabled":false,"domain":"media_player","service":"volume_set","areaId":[],"deviceId":[],"entityId":["media_player.echo_dot_escritorio"],"data":"{\"volume_level\":\"0.17\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":610,"y":2120,"wires":[["ecabb79ad1399bfc"]]},{"id":"ecabb79ad1399bfc","type":"api-call-service","z":"af740ccdec97ba89","name":"Campainha","server":"79fd2272.3f2bdc","version":5,"debugenabled":false,"domain":"notify","service":"alexa_media_echo_dot_escritorio","areaId":[],"deviceId":[],"entityId":[],"data":"{\"data\":{\"type\":\"announce\",\"method\":\"speak\"},\"message\":\"<voice name='Ines'><lang xml:lang='pt-PT'><amazon:auto-breaths frequency='low' volume='soft' duration='x-short'> Estão a tocar à campainha </amazon:auto-breaths></lang></voice>\",\"target\":[\"media_player.echo_dot_escritorio\"]}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"data"}],"queue":"none","x":870,"y":2120,"wires":[[]]},{"id":"52b744f93c1921e0","type":"switch","z":"af740ccdec97ba89","name":"códigos bridge","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"on","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":340,"y":2120,"wires":[["4322e1fcbbf89b87"]]},{"id":"c755ea95d74711a6","type":"server-state-changed","z":"af740ccdec97ba89","name":"Sensores","server":"79fd2272.3f2bdc","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"binary_sensor.campainha","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"on","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":140,"y":2120,"wires":[["52b744f93c1921e0"],[]]},{"id":"79fd2272.3f2bdc","type":"server","name":"Home Assistant :)","addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"","connectionDelay":false,"cacheJson":false,"heartbeat":false,"heartbeatInterval":"","statusSeparator":"","enableGlobalContextStore":false}]

Avisa que chegou correio pelo telegram.

[{"id":"79ce4183a8758d12","type":"telegrambot-notify","z":"34b3a010.928f3","name":"Correio","bot":"30ccc6d5795ddf03","chatId":"419345390","message":"","parseMode":"","x":800,"y":1600,"wires":[]},{"id":"6f7313167ab21a37","type":"server-state-changed","z":"34b3a010.928f3","name":"Sensor da caixa de correio","server":"79fd2272.3f2bdc","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"binary_sensor.correio","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"off","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"for":"2","forType":"num","forUnits":"seconds","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":270,"y":1600,"wires":[["1c223ed8291de5b3"],[]]},{"id":"1c223ed8291de5b3","type":"api-render-template","z":"34b3a010.928f3","name":"Prepare Message","server":"79fd2272.3f2bdc","version":0,"template":"Chegou a encomenda da Mauser.","resultsLocation":"payload","resultsLocationType":"msg","templateLocation":"template","templateLocationType":"msg","x":530,"y":1600,"wires":[["79ce4183a8758d12"]]},{"id":"30ccc3d5795ddf03","type":"telegrambot-config","botname":"@poisebebe_bot","usernames":"madmax","chatIds":"https://t.me/+IBklXAOGGrhiMDVk","pollInterval":"300"},{"id":"79fd2272.3f2bdc","type":"server","name":"Home Assistant :)","addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"","connectionDelay":false,"cacheJson":false,"heartbeat":false,"heartbeatInterval":"","statusSeparator":"","enableGlobalContextStore":false}]

Espero que não me tenha esquecido de partilhar nada :slight_smile: .

Agradecimentos ao chatgpt pela paciencia que tem tido comigo e ao @RodolfoVieira pela ajuda que deu.

3 Curtiram

Bom trabalho @andrefilipecruz
As placas pcb são realmente muito baratas .
Um projecto muito fixe

Se um vizinho replicar o teu codigo a tua campainha vai tocar

Sim, eu sei. Mas para evitar isso, o código tem uma chave de emparelhamento entre os dispositivos.

É só alterar a palavra “campainha1” para um código escolhido por nós.
image


Copyright © 2017-2021. Todos os direitos reservados
CPHA.pt - info@cpha.pt


FAQ | Termos de Serviço/Regras | Política de Privacidade