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
227 stars 121 forks source link

Manual Push switch not working when disconnected from internet. #380

Closed joeljana closed 1 month ago

joeljana commented 1 month ago

Controlling 2 relay using 2 push switch . working fine as expected.

Then I notice when the NodeMcu disconnected from the router the manual push switches are not working. Maybe the delay(250); in wifiSetup function was blocking the loop so i remove that from the while loop.

 while (WiFi.status() != WL_CONNECTED)
  {
    // Serial.printf(".");
    // delay(250);
  }

Now problem solved. Manual switch are working as expected after removing delay(250); even the NodeMcu is not connected to any router. ✅

Now im facing another issue . If Esp is connected to any router and but no internet connectivity during that time the manual push switch will stop working.

  1. Then I have to switch off the router to use the manual switch.
  2. Or have to wait for some time to use the manual switch for once. Then I have to wait for few seconds to use another switch.

I think SinricPro.handle(); In the void Loop Blocking others when there is no internet connectivity.

Need help ...

joeljana commented 1 month ago
// 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 <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

#include "SinricPro.h"
#include "SinricProSwitch.h"

#include <map>

#define WIFI_SSID         "JOEL"    
#define WIFI_PASS         "mi123456"
#define APP_KEY           "01583a71-43e5-450c-****************"      // Should look like "de0bxxxx-1x3x-4x3x-ax2x-5dabxxxxxxxx"
#define APP_SECRET        "b177b2a6-3de4-4653-81a1-02619ae7b*****************"   // Should look like "5f36xxxx-x3x7-4x3x-xexe-e86724a9xxxx-4c4axxxx-3x3x-x5xe-x9x3-333d65xxxxxx"

//Enter the device IDs here
#define device_ID_1   "664527df*********e1f59f"
#define device_ID_2   "66452818********3e1f59f"

// define the GPIO connected with Relays and switches
#define RelayPin1 5  
#define RelayPin2 4  

#define SwitchPin1 14  
#define SwitchPin2 12   

#define wifiLed   16   //D0

// comment the following line if you use a toggle switches instead of tactile buttons
#define TACTILE_BUTTON 1

#define BAUD_RATE   115200

#define DEBOUNCE_TIME 250

typedef struct {      // struct for the std::map below
  int relayPIN;
  int flipSwitchPIN;
} deviceConfig_t;

// this is the main configuration
// please put in your deviceId, the PIN for Relay and PIN for flipSwitch
// this can be up to N devices...depending on how much pin's available on your device ;)
// right now we have 4 devicesIds going to 4 relays and 4 flip switches to switch the relay manually
std::map<String, deviceConfig_t> devices = {
    //{deviceId, {relayPIN,  flipSwitchPIN}}
    {device_ID_1, {  RelayPin1, SwitchPin1 }},
    {device_ID_2, {  RelayPin2, SwitchPin2 }}

};

typedef struct {      // struct for the std::map below
  String deviceId;
  bool lastFlipSwitchState;
  unsigned long lastFlipSwitchChange;
} flipSwitchConfig_t;

std::map<int, flipSwitchConfig_t> flipSwitches;    // this map is used to map flipSwitch PINs to deviceId and handling debounce and last flipSwitch state checks
                                                  // it will be setup in "setupFlipSwitches" function, using informations from devices map

void setupRelays() { 
  for (auto &device : devices) {           // for each device (relay, flipSwitch combination)
    int relayPIN = device.second.relayPIN; // get the relay pin
    pinMode(relayPIN, OUTPUT);             // set relay pin to OUTPUT
    digitalWrite(relayPIN, HIGH);
  }
}

void setupFlipSwitches() {
  for (auto &device : devices)  {                     // for each device (relay / flipSwitch combination)
    flipSwitchConfig_t flipSwitchConfig;              // create a new flipSwitch configuration

    flipSwitchConfig.deviceId = device.first;         // set the deviceId
    flipSwitchConfig.lastFlipSwitchChange = 0;        // set debounce time
    flipSwitchConfig.lastFlipSwitchState = true;     // set lastFlipSwitchState to false (LOW)--

    int flipSwitchPIN = device.second.flipSwitchPIN;  // get the flipSwitchPIN

    flipSwitches[flipSwitchPIN] = flipSwitchConfig;   // save the flipSwitch config to flipSwitches map
    pinMode(flipSwitchPIN, INPUT_PULLUP);                   // set the flipSwitch pin to INPUT
  }
}

bool onPowerState(String deviceId, bool &state)
{
  Serial.printf("%s: %s\r\n", deviceId.c_str(), state ? "on" : "off");
  int relayPIN = devices[deviceId].relayPIN; // get the relay pin for corresponding device
  digitalWrite(relayPIN, !state);             // set the new relay state
  return true;
}

void handleFlipSwitches() {
  unsigned long actualMillis = millis();                                          // get actual millis
  for (auto &flipSwitch : flipSwitches) {                                         // for each flipSwitch in flipSwitches map
    unsigned long lastFlipSwitchChange = flipSwitch.second.lastFlipSwitchChange;  // get the timestamp when flipSwitch was pressed last time (used to debounce / limit events)

    if (actualMillis - lastFlipSwitchChange > DEBOUNCE_TIME) {                    // if time is > debounce time...

      int flipSwitchPIN = flipSwitch.first;                                       // get the flipSwitch pin from configuration
      bool lastFlipSwitchState = flipSwitch.second.lastFlipSwitchState;           // get the lastFlipSwitchState
      bool flipSwitchState = digitalRead(flipSwitchPIN);                          // read the current flipSwitch state
      if (flipSwitchState != lastFlipSwitchState) {                               // if the flipSwitchState has changed...
#ifdef TACTILE_BUTTON
        if (flipSwitchState) {                                                    // if the tactile button is pressed 
#endif      
          flipSwitch.second.lastFlipSwitchChange = actualMillis;                  // update lastFlipSwitchChange time
          String deviceId = flipSwitch.second.deviceId;                           // get the deviceId from config
          int relayPIN = devices[deviceId].relayPIN;                              // get the relayPIN from config
          bool newRelayState = !digitalRead(relayPIN);                            // set the new relay State
          digitalWrite(relayPIN, newRelayState);                                  // set the trelay to the new state

          SinricProSwitch &mySwitch = SinricPro[deviceId];                        // get Switch device from SinricPro
          mySwitch.sendPowerStateEvent(!newRelayState);                            // send the event
#ifdef TACTILE_BUTTON
        }
#endif      
        flipSwitch.second.lastFlipSwitchState = flipSwitchState;                  // update lastFlipSwitchState
      }
    }
  }
}

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

 while (WiFi.status() != WL_CONNECTED)  {
    handleFlipSwitches();
    yield();                              
  }

  digitalWrite(wifiLed, LOW);
  Serial.printf("connected!\r\n[WiFi]: IP-Address is %s\r\n", WiFi.localIP().toString().c_str());
}

//Setup Function For OTA Start
void setupOTA() {
  // Hostname defaults to esp3232-[MAC]
  ArduinoOTA.setHostname("SinricProOTATestDevice");

  // No authentication by default
  // ArduinoOTA.setPassword("admin");

  ArduinoOTA.onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH)
      type = "sketch";
    else // U_SPIFFS
      type = "filesystem";

    // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
    Serial.println("Start updating " + type);
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  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");
  });

  ArduinoOTA.begin();
  Serial.printf("\r\n[setupOTA]: Ready");
}
//Setup Function For OTA End

void setupSinricPro()
{
  for (auto &device : devices)
  {
    const char *deviceId = device.first.c_str();
    SinricProSwitch &mySwitch = SinricPro[deviceId];
    mySwitch.onPowerState(onPowerState);
  }

  SinricPro.begin(APP_KEY, APP_SECRET);
  SinricPro.restoreDeviceStates(true);
}

void setup()
{
  Serial.begin(BAUD_RATE);

  pinMode(wifiLed, OUTPUT);
  digitalWrite(wifiLed, HIGH);

  setupRelays();
  setupFlipSwitches();
  setupWiFi();
  setupSinricPro();
  setupOTA();
}

void loop()
{
  ArduinoOTA.handle();
  SinricPro.handle();
  handleFlipSwitches();
}
kakopappa commented 1 month ago

At the time you turn on the MCU, if there's no WiFi it is stuck in this while loop. so your handleFlipSwitches(); never gets called.

  while (WiFi.status() != WL_CONNECTED)
  {
    // Serial.printf(".");
    // delay(250);
  }
}
joeljana commented 1 month ago

At the time you turn on the MCU, if there's no internet it is stuck in this while loop. so your handleFlipSwitches(); never gets called.

  while (WiFi.status() != WL_CONNECTED)
  {
    // Serial.printf(".");
    // delay(250);
  }
}

Thats why i comment delay(250); . beacuse previously it was blocking whole loop when WL is not connected. After remove delay(250); the manual switches are working fine. even the MCU is not connected to any router.

But the problem is if the MCU is connected to any router without internet access the loop stuck at SinricPro.handle(); .

how to solve this problem sir?

Is there any way to prevent execute SinricPro.handle(); when there is no internet connection ?

sivar2311 commented 1 month ago

Calling SinricPro.handle() does not block the sketch. It's written in a non blocking manner. Maybe ArduinoOTA.handle() does when there is no WiFi connection. Comment this out and try again.

The while-loop in setup() is definitely blocking, regardless of whether there is a delay in it or not. The while-loop istself blocks until WiFi is connected! To make the buttons work while it is waiting for a WiFi connection simply change the while loop to:

  while (WiFi.status() != WL_CONNECTED)  {
    handleFlipSwitches();                                
  }
joeljana commented 1 month ago
while (WiFi.status() != WL_CONNECTED)  {
    handleFlipSwitches();
    yield();                                
  }

Sir its helpful when MCU is not connected to any wifi.

But I'm encountering a different issue. When MCU is connected to the router but there's no internet access. In that situation manual switches are not working properly.

Because some time internet disconnects from ISP sides. Then I have to switch off the router to satisfy this while-loop. Then I can use manual switches...

while (WiFi.status() != WL_CONNECTED)  {
    handleFlipSwitches();
    yield();                                
  }

I Comment out SinricPro.handle(). Surprisingly manual switch works again as expexted. Or Comment out SinricPro.begin(APP_KEY, APP_SECRET); do the same job for me.

May be the SinricPro library's connection attempt to its servers, which can cause the sketch to hang if the internet connection is unavailable.

So how to fix this issue....??? How to handle SinricPro connection SinricPro.handle() in a non-blocking manner When MCU is connected to the router but there's no internet access.

sivar2311 commented 1 month ago

I did a test (one button + one LED as a replacement for the relay). I cannot confirm the error. The button switches the LED, regardless of whether there is an Internet connection or not.

However, I used an ESP32 as I no longer have an ESP8266.

It may be that the underlying WebSocket library behaves differently on an ESP8266 than on an ESP32. Unfortunately, I cannot test this due to the lack of an ESP8266.

In general: The ESP8266 has been discontinued by Espressif. I would therefore recommend that you switch to an ESP32 or better ESP32-S3.

sivar2311 commented 1 month ago

Fortunately, I found an old ESP12e and I was able to test the same code here.

It does indeed block on the ESP8266. It is not a complete blockage, but there are delays of a few seconds.

However, the cause lies with the ESP8266 itself. I therefore repeat my recommendation to switch to the ESP32.

You could handle the code for the button via an interrupt routine. However, this must only switch the relay - not send the event! This could solve your problem on the ESP8266 - not tested.

joeljana commented 1 month ago

Thank you for your response, sir.

You are right. The ESP32 works perfectly, likely because its extra core allows the WebSocket library and other tasks to run simultaneously and more efficiently than the single-core ESP8266.

Mentors like you always inspire us. Thanks again!