Button Pressed 3 seconds to return to Access Point mode with ClientListener, and in the loop (); with thing.handle ();

Hi,

I’m not a programmer, but I’m evolving well with the Thinger.io library for Esp8266 (NodeMcu). I’m trying to implement a button that, when pressed for 3 seconds, the device goes back to Access Point mode.

I was able to implement the button when the device is in the loop () ;, running the thing.handle () ;.

But it is unable to implement the button when the device loses its connection or when the router / modem stops working for some electronic reason. This apparently causes an infinite loop, as the ClientListener is trying to connect to the configured router / modem without success infinitely.

The problem is if that router / modem has to be exchanged for another device. How to go back to Access Point mode, and change the SSID and password to connect to a new router / modem?

I already searched in other posts (with the words ClientListener and button), but I didn’t see anything about this issue.

The code for my button is this:

#define BUTTON_PIN  D8


   void buttonPressThreeSeconds(){

   // Read the pin on the button (with pulldown resistor). If true, run the code below:                      
   if(digitalRead(D8)){                      

                         
// Disconnect from the router / modem and return to Access Point mode.
  WiFi.persistent(true);
  WiFi.disconnect(true);
  WiFi.persistent(false);
  delay (1000); 
                    
  ESP.restart();
                                     
              }
  
}

I handled exceptions with this code, already provided in other posts:

class ClientListener : public ThingerWebConfig{
public:
    ClientListener(const char* user, const char* device, const char* device_credential) : 
      ThingerWebConfig(user, device, device_credential){}
protected:
  virtual void thinger_state_listener(THINGER_STATE state){
    
    ThingerWebConfig::thinger_state_listener(state);
    switch(state){
        case NETWORK_CONNECT_ERROR:

              digitalWrite(LED_PIN, HIGH);

              buttonPressThreeSeconds();
                             
            break;

        case NETWORK_CONNECTING:

              digitalWrite(LED_PIN, HIGH);

              buttonPressThreeSeconds();
                     
            break;
    }
  }
};

The above solution works, but I have to wait for ClientListener to start a new attempt for the button to be clicked. This can vary from 1 to 20 s, depending on the stage of the process.

What I need is for the button above to be pressed after being pressed for 3 seconds, for example.

If anyone has any suggestions, I appreciate it and I think other people can use this solution in their projects.

Hi,
Check this:

it detects when you reset your device very quick, and lets run alternative code, so you can run your reset routine when is detected the double reset.

Hope this helps.

Thanks for the sugestion.

I will try to implement.

But, apparently, this library can generate unwanted side effects.
If the power supply fluctuates (rapid power outages), turning the device on and off, the library may interpret it as a double reset and switch unduly to Access Point mode. I’m wrong?

The solution I made above does not change to Access Point with the button pressed for 3 seconds, but only when the device (ClientListener) ends and initiates a new connection attempt, which I return “NETWORK_CONNECTING” or “NETWORK_CONNECT_ERROR”. The problem is that the interval of attempts lasts about 30s. The user would have to keep the button pressed 30s in order to activate the command and switch to Access Point mode.

For these reasons, I am looking to implement a button pressed for 3 seconds to activate the Access Point mode, when there is a failure with the router / modem and the equipment needs to be changed. This will require changing the SSID and WiFi password of the device running ClientListener.

I tried to use the EasyButton library, in External Interrupts mode. But when the connection is lost, the ClientListener triggers the connection attempts and exits the loop (); . I think this does not allow the External Interrupts mode with the button pressed for 3 seconds to work correctly, according to the code below:

Example of the .ino library:

    /*
  Name:    InterruptsOnPressedFor.ino
  Created:  8/17/2019 10:16:52 AM
  Author: José Gabriel Companioni Benítez (https://github.com/elC0mpa)
  Description: Example to demostrate how to use onPressedFor functionality when using interrupts
*/

#include <Arduino.h>
#include <EasyButton.h>

/* 
  Arduino pin where the buttons are connected to.
  Should be a pin that supports external interrupts. 
 */
#define BUTTON_PIN 2

#define BAUDRATE 115200

// Instance of the button.
EasyButton button(BUTTON_PIN);

void buttonPressedTwoSeconds()
{
  Serial.println("Button pressed for two seconds");
}

void buttonISR()
{
  // When button is being used through external interrupts, parameter INTERRUPT must be passed to read() function.
  button.read();
}

void setup()
{
  // Initialize Serial for debuging purposes.
  Serial.begin(BAUDRATE);

  Serial.println();
  Serial.println(">>> EasyButton onPressedFor interrupt example <<<");

  // Initialize the button.
  button.begin();

  button.onPressedFor(2000, buttonPressedTwoSeconds);

  if (button.supportsInterrupt())
  {
    button.enableInterrupt(buttonISR);
    Serial.println("Button will be used through interrupts");
  }
}

void loop()
{
  /*
    update() function must be called repeatedly only if onPressedFor 
    functionality is being used and interrupt is enabled.
  */
  button.update();
}

Yes, I guess the DRD will reset the device even if there is a failure with the power supply that behaves as a double reset.

What if:

switch(state){
    case THINGER_AUTHENTICATED:
        // Everything is going well
        break;
    default:
        buttonPressThreeSeconds();
        break;
}

This should put the button into “listen mode” when the connection state is different from “THINGER_AUTHENTICATED”

I tried the suggestion, but it didn’t work for me.
I believe I have identified the “problem”.
I will express an opinion, but I am not a programmer.

In the file ThingerWifi.h, there is a piece of code that generates an infinite loop if the device cannot find the router/modem to connect.

This is the excerpt (lines 63 to 68):

        while( WiFi.status() != WL_CONNECTED) {
        if(millis() - wifi_timeout > 30000) return false;
        #ifdef ESP8266
        yield();
        #endif
    }

If the device does not find the router/modem that was registered, it will never return to Access Point mode, allowing the user to register a new router/modem with SSID and password different from those saved on the device.

This is easy to identify with the Arduino IDE Serial Monitor. But how to transmit this information to the human being who does not have an Arduino IDE Serial Monitor? And how to enable the human being to change the router/modem initially registered, with a new SSID and password?

NOTE: The Arduino IDE Serial Monitor displays the information:
[NETWORK] Connecting to network [“name of router/modem”]
[NETWORK] Cannot connect!
Without it, we would not know what is happening in the microcontroller (ESP8266, ESP32 …)

In my opinion, the first question can be through an LED (blink led) or buzzer (beeping every 200ms, for example).

The second question, there is a post with interesting reflections. The author of the post indicates the option that we already apply in our day to day: press a button for a specified time.

This is the approach used in other equipment such as router / modem, sonoff, Tuya …

The point is that the code in the file ThingerWifi.h causes a loop of 30 seconds, until triggering the switch (state) from the ClientListener example.This makes it impossible to use codes with the millis () function and other options.

So, the interesting thing, in my opinion, would be to change the section of “ThingerWifi.h” to allow the activation of an LED blink or buzzer (to indicate to the human being that something is wrong with the device) and add a routine of button, which allows the human being to try to connect the device to a new router / modem, with a new SSID and password.

The problem is, I don’t know how to do that. I am not a C / C ++ programmer. I only have experience in R language.

Therefore, I appeal to @alvarolb . Would it be possible to implement the option to enable an LED blink and / or buzzer when the device is trying to connect with the router / modem (which is damaged)? Would it be possible to implement the option of pressing a button for 3/5 seconds to activate the Access Point mode, when the router / modem (which is damaged)?

NOTE: The EasyButton library may be easy to implement with “ThingerWifi.h”, just like “ThingerTinyGSM.h” used the “TinyGSM” library.

Thanks for listening

I tried to get around this “problem” with the code below.
It works, but the inconvenient is that the user has to wait for the device’s LED to flash to know when to push the button to reset the WiFi settings (SSID and password).

I created two routines. The first is to enable the reset when the device connects to the modem / router, and is in the loop () ;.

The second is when the device cannot find the router / modem to connect to. The human being will be informed by the blinking led and then press the button to reset the WiFi settings (SSID and password), connect to a new router / modem.

Device: NodeMCU (ESP8266)

#include <ThingerWebConfig.h>

//Configuring Easybutton to meet the first routine
#include <EasyButton.h>

#define LED_PIN D0    
  
#define BUZZER_PIN  D5

#define BOTAO_PIN  D8

// Instructions for configuring the builder: https://easybtn.earias.me/docs/fundamentals#arguments
// I'm using a button with a pulldown resistor, so the arguments pullup_enable = false and invert = false
EasyButton button(BOTAO_PIN, 200, false, false);

int estado_led_offline;

void botao_reset_wifi_config();

class ClientListener : public ThingerWebConfig{
public:
    ClientListener(const char* user, const char* device, const char* device_credential) : 
      ThingerWebConfig(user, device, device_credential){}
protected:
  virtual void thinger_state_listener(THINGER_STATE state){
    
    ThingerWebConfig::thinger_state_listener(state);
    switch(state){
         case NETWORK_CONNECTING:
 //////////////////////////////////////////////////////////////////////////////////////////////////////////////
 //Second routine
            estado_led_offline = 0;
              
              for (int i = 0; i <= 9; i++) {                  
                  digitalWrite(LED_PIN, estado_led_offline);
                  delay(500);
                  estado_led_offline = !estado_led_offline;
              };

           if(digitalRead(BOTAO_PIN)){
                 
                  botao_reset_wifi_config();                            
            }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
                      
                  break;             
    }
  }
};

void setup() {

button.begin();
  button.onPressedFor(3000, botao_reset_wifi_config);

}

 void loop() {
     thing.handle();

  // Start button      
     button.read();

}

void botao_reset_wifi_config(){

                digitalWrite(LED_PIN, HIGH);
                pinMode(BUZZER_PIN, OUTPUT);
                delay(100);
                digitalWrite(LED_PIN, LOW);  
                pinMode(BUZZER_PIN, INPUT);
                 WiFi.persistent(true);
                 WiFi.disconnect(true);
                 WiFi.persistent(false);
                 delay(1000); 
                 
                 ESP.restart();                                                    
}

These two routines that I implemented to get around the “problem”.
But the second routine should work just like the first.

I tested both codes and they are working. But I would like the second routine to work just like the first.

Ok I have another suggestion,

What If you give a time lapse of about 5 seconds when the uC is rebooted before run the thinger.handle(); command, so the user may reset the uC and press the factory default button to get the credentials erased.

Basically is press two buttons sequence, and have already one onboard.

I would love to help you to modify the library, but Im not so experienced coder.

1 Like

Not sure if I correctly understand your question.
thinger.handle() will autometically try to reconnect to the thinger server until it fails for longer than 5 seconds. So you will have an extra 5 seconds delay when you call thinger.handle() in the loop, and that may cause error when you try to detect if the button is pressed for three seconds.
What I do in my real time system is to call thiner.handle() only when the wifi is working, and the system will go to offline mode if wifi is not available:
if(WiFi.status() == WL_CONNECTED)
{
thiner.handle();
}
else
{
buttonPressThreeSeconds();
}

I applied @ega 's suggestion. But your suggestion (@Hans_Wu ) is better suited to my problem. Excellent suggestions. Thanks to @ega and @Hans_Wu .

I applied this option [created a 7s window at the start of setup(); to make the reset]:

void setup() {

long wifi_reset = millis() + 7000;

while (millis() < wifi_reset) {
                 
      blink_LED_reset();

      button.read();

      yield();
};

}

But I thought your option was better. I’ll run tests on her.

void loop() {

  if(WiFi.status() == WL_CONNECTED) {

    thiner.handle();

  } else {

    blink_LED_reset();

    button.read();    

  };
}

I am happy that helps.
This solution solves most of my problems, but it still cause delay if the wifi is connected but the thinger server is temporally not responding due to unstable connection. In this case, thinger.handle() still blocks my system until the server responds, and that’s not acceptable for the design of a real-time system.
@ega is there any other conditions, other than WiFi.status() == WL_CONNECTED. I can use to check the connection between thinger and my device, so that I can skip thinger.handle() to avoid the delay caused by the unstable connection?

I woudl suggest you try with the client listener, check this topic:

With this conditions (and cloud as a global boolean variable of course):

switch(state){
    case THINGER_AUTHENTICATED:
        // Everything is going well
        cloud =1;
        break;
    default:
     cloud = 0;
        break;
}

And in the loop(); you may take decisions accordingly, note that you will need to call sometime the thing.handle() function to try to connect again.

On other hand, if you need to avoid timelapses without controlling a process, and the communication is secondary, I would recommend you use the ESP32, as it is dual core, so you can dedicate one core to the process control and the other to communicate, so one task will not interfere with the other, check this topic:

Hope this helps.

1 Like