sinricpro / esp8266-esp32-sdk

Library for https://sinric.pro - simple way to connect your device to Alexa, Google Home, SmartThings and cloud
https://sinric.pro
Other
234 stars 124 forks source link

Status of my smart lock #308

Closed ogabrielborges closed 1 year ago

ogabrielborges commented 1 year ago

Well, at the beginning of the code (before the void setup) I have a bool that when receiving lockState = false

my nodemcu releases the lock, waits 5 seconds, and locks it again.

It turns out that as it is a lock with an RFID module, in my void loop I would also like to make it release the lock, wait 5 seconds and close it again, but I don't know how to do this (I'm new to programming)

I'll leave the complete sketch code here

Here is the part of the code that is before the void setup, which when receiving lockState = false, it waits 5 seconds and returns lockState = true

bool onLockState(String deviceId, bool &lockState) {
    if (lockState == false) // se lockstate retornar false = destrancar
  {
    digitalWrite(LOCK_PIN, HIGH); // fechadura destranca
    delay(TIMING); // inicia um tempo de 5 segundos
    lockState = true; // mantenha o estado de bloqueio no servidor "bloqueado" porque ele é bloqueado novamente em 5 segundos automaticamente
    digitalWrite(LOCK_PIN, LOW); // fechadura tranca novamente
  } 
  Serial.printf("A fechadura foi %s\r\n", lockState?"fechada":"aberta"); // informa no monitor serial o estado da fechadura
  return true;
}

Here is the part of code in my void loop that is topic for the topic, I put two lines of code that tells SinricPro that the lock has been released, waits 5 seconds, and reports that it has been locked

if (conteudo.substring(1) == GABRIEL, LEONARDO){ // verifica se o ID do cartao lido tem o mesmo ID do cartao que queremos liberar o acesso
      Serial.println("Liberado!\r\n");
      digitalWrite(LedVerde, HIGH);
      digitalWrite(LOCK_PIN, HIGH);
      myLock.sendLockStateEvent(false);
      delay(TIMING);
      myLock.sendLockStateEvent(true);
      digitalWrite(LOCK_PIN, LOW);
      digitalWrite(LedVerde, LOW);
  }

I wonder if there is a way to make this code work as 'bool'

sivar2311 commented 1 year ago

Thanks for the new issue in the right place :)

  1. Don't use delay(), it will block your sketch!

SinricPro events and requests are queued.

      myLock.sendLockStateEvent(false);
      delay(TIMING);
      myLock.sendLockStateEvent(true);

This code will add 2 events to the event queue. The queue is processed next time SinricPro.handle() is called. This is not what you want.

Please take a look at the blink without delay concept to avoid blocking code.

  1. It makes no sense to send two events in such a short time one after the other. The backend systems (Alexa, Google Home, etc) need more time to update the status then you are sending events. Furthermore, SinricPro limits the number of events (sent in short succession) to avoid flooding the server.

Possible solution / program-logic:

Tip: Write a handler function for the timer, and call this function inside your loop() function. This will help you to keep your code clean and maintainable.

sivar2311 commented 1 year ago

Here is an example using some small helper functions (clean-code-style) to make the code and the logic more human-readable. I hope this will help you.

// Uncomment the following line to enable serial debug output
// #define ENABLE_DEBUG

#ifdef ENABLE_DEBUG
#define DEBUG_ESP_PORT Serial
#define NODEBUG_WEBSOCKETS
#define NDEBUG
#endif

#include <Arduino.h>
#ifdef ESP8266
#include <ESP8266WiFi.h>
#endif
#ifdef ESP32
#include <WiFi.h>
#endif

#include "SinricPro.h"
#include "SinricProLock.h"

#define WIFI_SSID "YOUR_WIFI_SSID"
#define WIFI_PASS "YOUR_WIFI_PASSWORD"
#define APP_KEY "YOUR_APP_KEY_HERE"        // Should look like "de0bxxxx-1x3x-4x3x-ax2x-5dabxxxxxxxx"
#define APP_SECRET "YOUR_APP_SECRET_HERE"  // Should look like "5f36xxxx-x3x7-4x3x-xexe-e86724a9xxxx-4c4axxxx-3x3x-x5xe-x9x3-333d65xxxxxx"
#define LOCK_ID "YOUR_DEVICE_ID_HERE"      // Should look like "5dc1564130xxxxxxxxxxxxxx"
#define BAUD_RATE 9600                     // Change baudrate to your need

#define AUTO_LOCK_TIME 5000
#define LOCK_GPIO 5

unsigned long lock_timer = 0;

void lock() {
    digitalWrite(LOCK_GPIO, HIGH);
}

void unlock() {
    digitalWrite(LOCK_GPIO, LOW);
}

void start_lock_timer() {
    lock_timer = millis();
}

void clear_lock_timer() {
    lock_timer = 0;
}

bool lock_timer_has_expired() {
    if (lock_timer == 0) return false;

    unsigned long passed_time = millis() - lock_timer;
    return passed_time >= AUTO_LOCK_TIME;
}

void auto_lock() {
    if (lock_timer_has_expired() == true) {
        clear_lock_timer();
        lock();
    }
}

bool onLockState(String deviceId, bool &lockState) {
    if (lockState == false) {
        unlock();
        start_lock_timer();
        // let the server know that the lock is "still" locked 
        // (ignoring the 5 seconds of unlocked state)
        lockState = true;  
    } else {
        lock();
    }
    return true;
}

void setupWiFi() {
    Serial.printf("\r\n[Wifi]: Connecting");
    WiFi.begin(WIFI_SSID, WIFI_PASS);

    while (WiFi.status() != WL_CONNECTED) {
        Serial.printf(".");
        delay(250);
    }
    IPAddress localIP = WiFi.localIP();
    Serial.printf("connected!\r\n[WiFi]: IP-Address is %d.%d.%d.%d\r\n", localIP[0], localIP[1], localIP[2], localIP[3]);
}

void setupSinricPro() {
    SinricProLock &myLock = SinricPro[LOCK_ID];
    myLock.onLockState(onLockState);

    // setup SinricPro
    SinricPro.onConnected([]() { Serial.printf("Connected to SinricPro\r\n"); });
    SinricPro.onDisconnected([]() { Serial.printf("Disconnected from SinricPro\r\n"); });
    SinricPro.begin(APP_KEY, APP_SECRET);
}

void setup() {
    Serial.begin(BAUD_RATE);
    Serial.printf("\r\n\r\n");
    setupWiFi();
    setupSinricPro();
}

void loop() {
    SinricPro.handle();
    auto_lock();
}
ogabrielborges commented 1 year ago

Thanks for the tip about using milis() instead of delay(), I confess that I only now managed to learn how to use it, because before I had difficulty using milis()

But there is a catch about the code you quoted in the void loop

  myLock.sendLockStateEvent(false);
  delay(TIMING);
  myLock.sendLockStateEvent(true);

I wanted to let the server know that the smart lock was unlocked by a method other than sinricpro (in this case, I'm using rfid)

count the time, and block her again.

How would it be?

So I won't need to send two events to sinricpro in such a short time to change the state of the smart lock

sivar2311 commented 1 year ago

I wanted to let the server know that the smart lock was unlocked by a method other than sinricpro (in this case, I'm using rfid)

Sorry... my mistake.

When the lock is unlocked by a RFID-Card, will there be also a auto-lock after 5 seconds?

ogabrielborges commented 1 year ago

Sim :)

sivar2311 commented 1 year ago

Sim ?

ogabrielborges commented 1 year ago

Yes, Sorry, it's force of habit, I'm Brazilian!

sivar2311 commented 1 year ago

I don't know about RFID-Card reader. You have to handle this in your code. Can you provide an example code for handling the RFID-Card reader so I can help you?

(I'll be out for about 1 hour, so my next response may take a bit longer)

ogabrielborges commented 1 year ago

This is the RFID library I'm using https://github.com/miguelbalboa/rfid

Here is the part of my code that when reading an authorized card, ESP executes the code This happens inside the void loop

  if (conteudo.substring(1) == GABRIEL, LEONARDO){  //when reading an authorized tag run this code
     Serial.println("Smart Lock OPEN!\r\n");             //shows the message on the serial monitor
     digitalWrite(LOCK_PIN, HIGH);              //open the smart lock
     myLock.sendLockStateEvent(false); //informs sinricpro that the smart lock has been unlocked
     delay(TEMPO_FECHAR); //wait 5 seconds to close the smart lock
     digitalWrite(LOCK_PIN, LOW);              //close the smart lock
     myLock.sendLockStateEvent(true);       //informs sinricpro that the smart lock has been locked  
  }
ogabrielborges commented 1 year ago

This code is a quick-fix because I don't know how to make it work as a bool at the beginning of the code, can you improve this?

sivar2311 commented 1 year ago

Added functions:

// Uncomment the following line to enable serial debug output
// #define ENABLE_DEBUG

#ifdef ENABLE_DEBUG
#define DEBUG_ESP_PORT Serial
#define NODEBUG_WEBSOCKETS
#define NDEBUG
#endif

#include <Arduino.h>
#ifdef ESP8266
#include <ESP8266WiFi.h>
#endif
#ifdef ESP32
#include <WiFi.h>
#endif

#include <MFRC522.h>
#include <SPI.h>

#include "SinricPro.h"
#include "SinricProLock.h"

#define WIFI_SSID "YOUR_WIFI_SSID"
#define WIFI_PASS "YOUR_WIFI_PASSWORD"
#define APP_KEY "YOUR_APP_KEY_HERE"
#define APP_SECRET "YOUR_APP_SECRET_HERE"
#define LOCK_ID "YOUR_DEVICE_ID_HERE"
#define BAUD_RATE 9600

#define RST_PIN D3    // define o pino rst na gpio do modulo MFRC522
#define SS_PIN D8     // define o pino ss/sda na gpio do modulo MFRC522
#define LED_AVISO D1  // define o pino do led na gpio do modulo MFRC522
#define LOCK_PIN D2   // [PROJETO] Pino do relé (D5 = GPIO 14 no ESP8266)

#define AUTO_LOCK_TIME 5000

MFRC522 mfrc522(SS_PIN, RST_PIN);  // Cria uma instancia MFRC522

unsigned long lock_timer = 0;

void lock() {
    digitalWrite(LOCK_PIN, HIGH);
}

void unlock() {
    digitalWrite(LOCK_PIN, LOW);
}

void start_lock_timer() {
    lock_timer = millis();
}

void clear_lock_timer() {
    lock_timer = 0;
}

bool lock_timer_has_expired() {
    if (lock_timer == 0) return false;

    unsigned long passed_time = millis() - lock_timer;
    return passed_time >= AUTO_LOCK_TIME;
}

void auto_lock() {
    if (lock_timer_has_expired() == true) {
        clear_lock_timer();
        lock();
    }
}

bool RFID_card_is_present() {
    return (mfrc522.PICC_IsNewCardPresent() == true && mfrc522.PICC_ReadCardSerial() == true);
}

bool RFID_card_is_valid() {
    char hex_buf[11]{0};

    for (byte i = 0; i < mfrc522.uid.size; i++) {
        sprintf(&hex_buf[i * 2], "%02X", mfrc522.uid.uidByte[i]);
    }
    String conteudo(hex_buf);

    return conteudo == "3706D73A" || conteudo == "031CA0F6";
}

void handleRFID() {
    if (RFID_card_is_present() && RFID_card_is_valid()) {
        unlock();
        start_lock_timer();
    }
}

bool onLockState(String deviceId, bool &lockState) {
    if (lockState == false) {
        unlock();
        start_lock_timer();
        // let the server know that the lock is "still" locked
        // (ignoring the 5 seconds of unlocked state)
        lockState = true;
    } else {
        lock();
    }
    return true;
}

void setupWiFi() {
    Serial.printf("\r\n[Wifi]: Connecting");
    WiFi.begin(WIFI_SSID, WIFI_PASS);

    while (WiFi.status() != WL_CONNECTED) {
        Serial.printf(".");
        delay(250);
    }
    Serial.printf("connected!\r\n[WiFi]: IP-Address is %s\r\n", WiFi.localIP().toString().c_str());
}

void setupRFID() {
    SPI.begin();         // Inicia  SPI bus
    mfrc522.PCD_Init();  // Inicia MFRC522
}

void setupLock() {
    pinMode(LOCK_PIN, OUTPUT);  // [PROJETO] definir o pino do relé para SAÍDA
}

void setupSinricPro() {
    SinricProLock &myLock = SinricPro[LOCK_ID];
    myLock.onLockState(onLockState);

    // setup SinricPro
    SinricPro.onConnected([]() { Serial.printf("Connected to SinricPro\r\n"); });
    SinricPro.onDisconnected([]() { Serial.printf("Disconnected from SinricPro\r\n"); });
    SinricPro.begin(APP_KEY, APP_SECRET);
}

void setup() {
    Serial.begin(BAUD_RATE);
    Serial.printf("\r\n\r\n");
    setupLock();
    setupRFID();
    setupWiFi();
    setupSinricPro();
}

void loop() {
    SinricPro.handle();
    handleRFID();
    auto_lock();
}
ogabrielborges commented 1 year ago

From what I understand, in this code, there is nothing that informs SinricPro that the lock has been unlocked, I need this because I am going to configure Alexa to notify me that the lock has been unlocked (even outside of sinricpro)

for that I was using myLock.setLockStateEvent() function;

that's the purpose of help

sivar2311 commented 1 year ago

Ok, in this case you have to send the lockStateEvent.

Modified functions:

void handleRFID() {
    if (RFID_card_is_present() && RFID_card_is_valid()) {
        unlock();
        start_lock_timer();
        send_lock_state(false);
    }
}

void auto_lock() {
    if (lock_timer_has_expired() == true) {
        clear_lock_timer();
        lock();
        send_lock_state(true);
    }
}
ogabrielborges commented 1 year ago

This is cool, it worked perfectly as I would like the part to release the lock with RFID, it was so cool that when the project is finished I will be happy to post it here on github for more people to discover the power of your esp with sinricpro!

One more thing: By releasing the lock with an rfid tag, the system works perfectly as I need it, and sinricpro updates the state of the lock exactly as needed!

However, when I unlock the lock through sinricpro, alexa or google home, the code does not return to sinricpro that the lock was unlocked, it only returns that it was locked after the time expires!

How to solve?

sivar2311 commented 1 year ago

Yes, we have explicitly prevented this by setting lockState = true; in the onLockState callback. Just remove this line.

ogabrielborges commented 1 year ago

No words to thank you, this is the project of a very simple smart lock for the door of my house that receives few visits, but I decided to do the project to learn a little more about programming and hardware, since I recently entered college!

The problem was completely solved, my code was super clean and without any glitches

Thank you my Friend ;)

ogabrielborges commented 1 year ago

You can implement the following in void handleRFID:

OBS: I tried in some ways to implement, starting by inserting else, but when I passed the rfid card in the reader, my serial monitor was flooded with messages, since it didn't work, I'm asking you to do it so I can learn more

sivar2311 commented 1 year ago

To make the code more clean, I have implemented new functions, changed some functions and renamed a few existing functions. So, please read the new code from top to bottom ;)

There is also an Array of valid card-id's now, which allows you to add new (valid) cards very easily.

// #define ENABLE_DEBUG

#ifdef ENABLE_DEBUG
#define DEBUG_ESP_PORT Serial
#define NODEBUG_WEBSOCKETS
#define NDEBUG
#endif

#include <Arduino.h>
#ifdef ESP8266
#include <ESP8266WiFi.h>
#endif
#ifdef ESP32
#include <WiFi.h>
#endif

#include <MFRC522.h>
#include <SPI.h>

#include "SinricPro.h"
#include "SinricProLock.h"

#define WIFI_SSID  "YOUR_WIFI_SSID"
#define WIFI_PASS  "YOUR_WIFI_PASSWORD"
#define APP_KEY    "YOUR_APP_KEY_HERE"
#define APP_SECRET "YOUR_APP_SECRET_HERE"
#define LOCK_ID    "YOUR_DEVICE_ID_HERE"
#define BAUD_RATE  9600

#define RST_PIN   D3  // define o pino rst na gpio do modulo MFRC522
#define SS_PIN    D8  // define o pino ss/sda na gpio do modulo MFRC522
#define LED_AVISO D1  // define o pino do led na gpio do modulo MFRC522
#define LOCK_PIN  D2  // [PROJETO] Pino do relé (D5 = GPIO 14 no ESP8266)

#define AUTO_LOCK_TIME 5000

MFRC522 mfrc522(SS_PIN, RST_PIN);  // Cria uma instancia MFRC522

unsigned long lock_timer = 0;

// Array of valid card-IDs
String valid_card_ids[] = {
    "3706D73A",
    "031CA0F6"
};

void lock() {
    digitalWrite(LOCK_PIN, HIGH);
    Serial.println("Door is locked now.");
}

void unlock() {
    digitalWrite(LOCK_PIN, LOW);
    Serial.println("Door is unlocked now.");
}

void start_lock_timer() {
    lock_timer = millis();
    Serial.println("Auto-lock-timer started");
}

void clear_lock_timer() {
    lock_timer = 0;
}

bool lock_timer_has_expired() {
    if (lock_timer == 0) return false;

    unsigned long passed_time = millis() - lock_timer;
    return passed_time >= AUTO_LOCK_TIME;
}

void unlock_with_auto_relock() {
    unlock();
    start_lock_timer();
}

void send_lock_state(bool state) {
    SinricProLock &myLock = SinricPro[LOCK_ID];
    myLock.sendLockStateEvent(state);
}

void auto_lock() {
    if (lock_timer_has_expired() == true) {
        Serial.println("Auto-lock-timer has expired");

        clear_lock_timer();
        lock();
        send_lock_state(true);
    }
}

bool RFID_card_is_present() {
    return (mfrc522.PICC_IsNewCardPresent() == true && mfrc522.PICC_ReadCardSerial() == true);
}

bool RFID_card_is_not_present() {
    return !RFID_card_is_present();
}

String get_RFID_card_ID() {
    char hex_buf[11]{0};

    for (byte i = 0; i < mfrc522.uid.size; i++) {
        sprintf(&hex_buf[i * 2], "%02X", mfrc522.uid.uidByte[i]);
    }

    return String(hex_buf);
}

bool validate_RFID_card(String card_id) {
    for (auto &valid_id : valid_card_ids) {
        if (card_id == valid_id) return true;
    }

    return false;
}

void handleRFID() {
    if (RFID_card_is_not_present()) return;

    String card_id            = get_RFID_card_ID();
    bool   RFID_card_is_valid = validate_RFID_card(card_id);

    if (RFID_card_is_valid) {
        Serial.printf("RFID-Card \"%s\" is valid.\r\n", card_id.c_str());

        unlock_with_auto_relock();
        send_lock_state(false);
    } else {
        Serial.printf("RFID-Card \"%s\" is invalid.\r\n", card_id.c_str());
    }
}

bool onLockState(String deviceId, bool &lockState) {
    if (lockState == false) {
        unlock_with_auto_relock();
    } else {
        lock();
    }
    return true;
}

void setupWiFi() {
    Serial.printf("\r\n[Wifi]: Connecting");
    WiFi.begin(WIFI_SSID, WIFI_PASS);

    while (WiFi.status() != WL_CONNECTED) {
        Serial.printf(".");
        delay(250);
    }
    Serial.printf("connected!\r\n[WiFi]: IP-Address is %s\r\n", WiFi.localIP().toString().c_str());
}

void setupRFID() {
    SPI.begin();         // Inicia  SPI bus
    mfrc522.PCD_Init();  // Inicia MFRC522
}

void setupLock() {
    pinMode(LOCK_PIN, OUTPUT);  // [PROJETO] definir o pino do relé para SAÍDA
}

void setupSinricPro() {
    SinricProLock &myLock = SinricPro[LOCK_ID];
    myLock.onLockState(onLockState);

    // setup SinricPro
    SinricPro.onConnected([]() { Serial.printf("Connected to SinricPro\r\n"); });
    SinricPro.onDisconnected([]() { Serial.printf("Disconnected from SinricPro\r\n"); });
    SinricPro.begin(APP_KEY, APP_SECRET);
}

void setup() {
    Serial.begin(BAUD_RATE);
    Serial.printf("\r\n\r\n");
    setupLock();
    setupRFID();
    setupWiFi();
    setupSinricPro();
}

void loop() {
    SinricPro.handle();
    handleRFID();
    auto_lock();
}
ogabrielborges commented 1 year ago

When I insert a tag that is not registered, how do I prevent the chat from being spammed with unregistered tag messages? Something like just issuing the message "tag not registered" only 1 time every 5 seconds for example

image

kakopappa commented 1 year ago

just noticed you have a public repo for this with credentials, you should consider removing secrets.

sivar2311 commented 1 year ago

Nice to see that the code is working for you and that you like it. :) Although I don't have an RFID reader myself and therefore can't test it.

I assumed that the RFID library function mfrc522.PICC_IsNewCardPresent() only fires once when a card is presented. So I think this question can be better answered in the RFID library repository.

ogabrielborges commented 1 year ago

Okay, thanks a lot for your help, and I'm really sorry to bother you so much!

ogabrielborges commented 1 year ago

acabei de perceber que você tem um repositório público para isso com credenciais, considere a remoção de segredos.

Damn, thanks so much for letting me know! I will remove I'll also credit you for your help with the code, you guys helped me a lot and did a lot of the work for this project to work.

Thank you :)

sivar2311 commented 1 year ago

You're welcome!