arkhipenko / TaskScheduler

Cooperative multitasking for Arduino, ESPx, STM32, nRF and other microcontrollers
http://playground.arduino.cc/Code/TaskScheduler
BSD 3-Clause "New" or "Revised" License
1.26k stars 230 forks source link

Cannot connect to Access Point #71

Closed abhishek2184 closed 5 years ago

abhishek2184 commented 5 years ago

Hi, I am using ESP01 board from AI Thinker and I am not able to connect to my wifi router when I execute the yield example(Schedule_example14_yield.ino ). I have changed the SSID and password. I have also removed one of the two LEDs and assigned "2" instead of the initial "D0" pin since I am not using the Node MCU. I am using a standard 2.4GHz wifi router. WiFi.begin() does connect me to the network when I don't use the TaskScheduler.h library.

abhishek2184 commented 5 years ago
`/**
      This test illustrates the use if yield methods and internal StatusRequest objects
      THIS TEST HAS BEEN TESTED ON NODEMCU V.2 (ESP8266)

      The WiFi initialization and NTP update is executed in parallel to blinking the onboard LED
      and an external LED connected to D2 (GPIO04)
      Try running with and without correct WiFi parameters to observe the difference in behaviour
*/

#define _TASK_SLEEP_ON_IDLE_RUN
#define _TASK_STATUS_REQUEST
#include <TaskScheduler.h>

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>

Scheduler ts;

// Callback methods prototypes
void connectInit();
void ledCallback();
bool ledOnEnable();
void ledOnDisable();
void ledOn();
void ledOff();
void ntpUpdateInit();

// Tasks

Task  tConnect    (TASK_SECOND, TASK_FOREVER, &connectInit, &ts, true);
Task  tLED        (TASK_IMMEDIATE, TASK_FOREVER, &ledCallback, &ts, false, &ledOnEnable, &ledOnDisable);

// Tasks running on events
Task  tNtpUpdate  (&ntpUpdateInit, &ts);

// Replace with WiFi parameters of your Access Point/Router:
const char *ssid  =  "myssid";
const char *pwd   =  "************";

long  ledDelayOn, ledDelayOff;

#define LEDPIN            2      // Onboard LED pin - linked to WiFi
//#define LEDPIN2           D2      // External LED
#define CONNECT_TIMEOUT   30      // Seconds
#define CONNECT_OK        0       // Status of successful connection to WiFi
#define CONNECT_FAILED    (-99)   // Status of failed connection to WiFi

// NTP Related Definitions
#define NTP_PACKET_SIZE  48       // NTP time stamp is in the first 48 bytes of the message

IPAddress     timeServerIP;       // time.nist.gov NTP server address
const char*   ntpServerName = "time.nist.gov";
byte          packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
unsigned long epoch;

WiFiUDP udp;                      // A UDP instance to let us send and receive packets over UDP

#define LOCAL_NTP_PORT  2390      // Local UDP port for NTP update

void setup() {
  Serial.begin(115200);
  Serial.println(F("TaskScheduler test #14 - Yield and internal StatusRequests"));
  Serial.println(F("=========================================================="));
  Serial.println();

  pinMode (LEDPIN, OUTPUT);
//  pinMode (LEDPIN2, OUTPUT);

  tNtpUpdate.waitFor( tConnect.getInternalStatusRequest() );  // NTP Task will start only after connection is made
}

void loop() {
  ts.execute();                   // Only Scheduler should be executed in the loop
}

/**
   Initiate connection to the WiFi network
*/
void connectInit() {
  Serial.print(millis());
  Serial.println(F(": connectInit."));
  Serial.println(F("WiFi parameters: "));
  Serial.print(F("SSID: ")); Serial.println(ssid);
  Serial.print(F("PWD : ")); Serial.println(pwd);

  WiFi.mode(WIFI_STA);
  WiFi.hostname("esp8266");
  WiFi.begin(ssid, pwd);
  yield();

  ledDelayOn = TASK_SECOND / 2;
  ledDelayOff = TASK_SECOND / 4;
  tLED.enable();

  tConnect.yield(&connectCheck);            // This will pass control back to Scheduler and then continue with connection checking
}

/**
   Periodically check if connected to WiFi
   Re-request connection every 5 seconds
   Stop trying after a timeout
*/
void connectCheck() {
  Serial.print(millis());
  Serial.println(F(": connectCheck."));

  if (WiFi.status() == WL_CONNECTED) {                // Connection established
    Serial.print(millis());
    Serial.print(F(": Connected to AP. Local ip: "));
    Serial.println(WiFi.localIP());
    tConnect.disable();
  }
  else {

    if (tConnect.getRunCounter() % 5 == 0) {          // re-request connection every 5 seconds

      Serial.print(millis());
      Serial.println(F(": Re-requesting connection to AP..."));

      WiFi.disconnect(true);
      yield();                                        // This is an esp8266 standard yield to allow linux wifi stack run
      WiFi.hostname("esp8266");
      WiFi.mode(WIFI_STA);
      WiFi.begin(ssid, pwd);
      yield();                                        // This is an esp8266 standard yield to allow linux wifi stack run
    }

    if (tConnect.getRunCounter() == CONNECT_TIMEOUT) {  // Connection Timeout
      tConnect.getInternalStatusRequest()->signal(CONNECT_FAILED);  // Signal unsuccessful completion
      tConnect.disable();

      Serial.print(millis());
      Serial.println(F(": connectOnDisable."));
      Serial.print(millis());
      Serial.println(F(": Unable to connect to WiFi."));

      ledDelayOn = TASK_SECOND / 16;                  // Blink LEDs quickly due to error
      ledDelayOff = TASK_SECOND / 16;
      tLED.enable();
    }
  }
}

/**
   Initiate NTP update if connection was established
*/
void ntpUpdateInit() {
  Serial.print(millis());
  Serial.println(F(": ntpUpdateInit."));

  if ( tConnect.getInternalStatusRequest()->getStatus() != CONNECT_OK ) {  // Check status of the Connect Task
    Serial.print(millis());
    Serial.println(F(": cannot update NTP - not connected."));
    return;
  }

  udp.begin(LOCAL_NTP_PORT);
  if ( WiFi.hostByName(ntpServerName, timeServerIP) ) { //get a random server from the pool

    Serial.print(millis());
    Serial.print(F(": timeServerIP = "));
    Serial.println(timeServerIP);

    sendNTPpacket(timeServerIP); // send an NTP packet to a time server
  }
  else {
    Serial.print(millis());
    Serial.println(F(": NTP server address lookup failed."));
    tLED.disable();
    udp.stop();
    tNtpUpdate.disable();
    return;
  }

  ledDelayOn = TASK_SECOND / 8;
  ledDelayOff = TASK_SECOND / 8;
  tLED.enable();

  tNtpUpdate.set( TASK_SECOND, CONNECT_TIMEOUT, &ntpCheck );
  tNtpUpdate.enableDelayed();
}

/**
 * Check if NTP packet was received
 * Re-request every 5 seconds
 * Stop trying after a timeout
 */
void ntpCheck() {
  Serial.print(millis());
  Serial.println(F(": ntpCheck."));

  if ( tNtpUpdate.getRunCounter() % 5 == 0) {

    Serial.print(millis());
    Serial.println(F(": Re-requesting NTP update..."));

    udp.stop();
    yield();
    udp.begin(LOCAL_NTP_PORT);
    sendNTPpacket(timeServerIP);
    return;
  }

  if ( doNtpUpdateCheck()) {
    Serial.print(millis());
    Serial.println(F(": NTP Update successful"));

    Serial.print(millis());
    Serial.print(F(": Unix time = "));
    Serial.println(epoch);

    tLED.disable();
    tNtpUpdate.disable();
    udp.stop();
  }
  else {
    if ( tNtpUpdate.isLastIteration() ) {
      Serial.print(millis());
      Serial.println(F(": NTP Update failed"));
      tLED.disable();
      udp.stop();
    }
  }
}

/**
 * Send NTP packet to NTP server
 */
unsigned long sendNTPpacket(IPAddress & address)
{
  Serial.print(millis());
  Serial.println(F(": sendNTPpacket."));

  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  udp.beginPacket(address, 123); //NTP requests are to port 123
  udp.write(packetBuffer, NTP_PACKET_SIZE);
  udp.endPacket();
  yield();
}

/**
 * Check if a packet was recieved.
 * Process NTP information if yes
 */
bool doNtpUpdateCheck() {

  Serial.print(millis());
  Serial.println(F(": doNtpUpdateCheck."));

  yield();
  int cb = udp.parsePacket();
  if (cb) {
    // We've received a packet, read the data from it
    udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

    //the timestamp starts at byte 40 of the received packet and is four bytes,
    // or two words, long. First, esxtract the two words:

    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    // combine the four bytes (two words) into a long integer
    // this is NTP time (seconds since Jan 1 1900):
    unsigned long secsSince1900 = highWord << 16 | lowWord;

    // now convert NTP time into everyday time:
    // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
    const unsigned long seventyYears = 2208988800UL;
    // subtract seventy years:
    epoch = secsSince1900 - seventyYears;
    return (epoch != 0);
  }
  return false;
}

/**
 * Flip the LED state based on the current state
 */
bool ledState;
void ledCallback() {
  if ( ledState ) ledOff();
  else ledOn();
}

/**
 * Make sure the LED starts lit
 */
bool ledOnEnable() {
  ledOn();
  return true;
}

/**
 * Make sure LED ends dimmed
 */
void ledOnDisable() {
  ledOff();
}

/**
 * Turn LEDs on. 
 * Set appropriate delay.
 * PLEASE NOTE: NodeMCU onbaord LED is active-low
 */
void ledOn() {
  ledState = true;
  digitalWrite(LEDPIN, LOW);
  //digitalWrite(LEDPIN2, HIGH);
  tLED.delay( ledDelayOn );
}

/**
 * Turn LEDs off. 
 * Set appropriate delay.
 * PLEASE NOTE: NodeMCU onbaord LED is active-low
 */
void ledOff() {
  ledState = false;
  digitalWrite(LEDPIN, HIGH);
//  digitalWrite(LEDPIN2, LOW);
  tLED.delay( ledDelayOff );
}
`

This is the final Code if that helps.

arkhipenko commented 5 years ago

I suspect this is what is happening. When I played with ESP, my experience was if it does not connect within, say, 5 seconds to the AP it will not connect at all. Therefore I implemented every 5 seconds retry here.

It is quite possible that 5 seconds is not enough in your case. Could you please change this line:

if (tConnect.getRunCounter() % 5 == 0) { // re-request connection every 5 seconds

to

if (tConnect.getRunCounter() % 10 == 0) { // re-request connection every 10 seconds

giving the chip 10 seconds to connect between attempts? If 10 is too short, just keep increasing the number until you hit the overall timeout of 30 seconds here.

#define CONNECT_TIMEOUT 30 // Seconds

Same thing for the NTP call here.

if ( tNtpUpdate.getRunCounter() % 5 == 0) {

Same 5 seconds. I should have parameterized it of course...

Hope this resolves the issue. Please let me know if it helped.

abhishek2184 commented 5 years ago

Hi, Thanks for the suggestion, but even this is not working. I've modified re-request interval as: if (tConnect.getRunCounter() % 30 == 0) {

I've also changed the connect_timeout value to 90 seconnds #define CONNECT_TIMEOUT 90 // Seconds

The serial monitor keeps on printing Serial.println(F(": connectCheck.")); for 30 seconds before re-requesting the connection. Without the library, my hardware gets connected to the network in less than 10 seconds.

Can you please suggest some other troubleshooting step.

Thanks!

abhishek2184 commented 5 years ago

Hi, Thanks for the suggestion, but even this is not working. I've modified re-request interval as: if (tConnect.getRunCounter() % 30 == 0) {

I've also changed the connect_timeout value to 90 seconnds #define CONNECT_TIMEOUT 90 // Seconds

The serial monitor keeps on printing Serial.println(F(": connectCheck.")); for 30 seconds before re-requesting the connection. Without the library, my hardware gets connected to the network in less than 10 seconds.

Can you please suggest some other troubleshooting step.

Thanks!

Big Update!

When I changed if (tConnect.getRunCounter() % 30 == 0) { to if (tConnect.getRunCounter() % 60 == 0) { and increased the Timeout value to 100, it finally got connected to the network!

Thanks a lot for your help! You saved the day :)

arkhipenko commented 5 years ago

Can you post your code which connects in 10 seconds? It is still a big difference between 10 and a 100! Should not be that long.

abhishek2184 commented 5 years ago

Here, on initiating the function InitWifi(), the esp gets connected to the wifi in no time.

#include <ArduinoJson.h>
#include <PubSubClient.h>
#include <ESP8266WiFi.h>

#define WIFI_SSID "myssid"
#define WIFI_PASSWORD "*************"

#define TOKEN "*******************"

#define GPIO0 1
#define GPIO2 2

#define GPIO0_PIN 3
#define GPIO2_PIN 5

char thingsboardServer[] = "demo.thingsboard.io";

WiFiClient wifiClient;

PubSubClient client(wifiClient);

int status = WL_IDLE_STATUS;

// We assume that all GPIOs are LOW
boolean gpioState[] = {false, false};

void setup() {
  Serial.begin(115200);
  // Set output mode for all GPIO pins
  pinMode(GPIO0, OUTPUT);
  pinMode(GPIO2, OUTPUT);
  delay(10);
  InitWiFi();
  client.setServer( thingsboardServer, 1883 );
  client.setCallback(on_message);
}

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

  client.loop();
}

// The callback for when a PUBLISH message is received from the server.
void on_message(const char* topic, byte* payload, unsigned int length) {

  Serial.println("On message");

  char json[length + 1];
  strncpy (json, (char*)payload, length);
  json[length] = '\0';

  Serial.print("Topic: ");
  Serial.println(topic);
  Serial.print("Message: ");
  Serial.println(json);

  // Decode JSON request
  StaticJsonBuffer<200> jsonBuffer;
  JsonObject& data = jsonBuffer.parseObject((char*)json);

  if (!data.success())
  {
    Serial.println("parseObject() failed");
    return;
  }

  // Check request method
  String methodName = String((const char*)data["method"]);

  if (methodName.equals("getGpioStatus")) {
    // Reply with GPIO status
    String responseTopic = String(topic);
    responseTopic.replace("request", "response");
    client.publish(responseTopic.c_str(), get_gpio_status().c_str());
  } else if (methodName.equals("setGpioStatus")) {
    // Update GPIO status and reply
    set_gpio_status(data["params"]["pin"], data["params"]["enabled"]);
    String responseTopic = String(topic);
    responseTopic.replace("request", "response");
    client.publish(responseTopic.c_str(), get_gpio_status().c_str());
    client.publish("v1/devices/me/attributes", get_gpio_status().c_str());
  }
}

String get_gpio_status() {
  // Prepare gpios JSON payload string
  StaticJsonBuffer<200> jsonBuffer;
  JsonObject& data = jsonBuffer.createObject();
  data[String(GPIO0_PIN)] = gpioState[0] ? true : false;
  data[String(GPIO2_PIN)] = gpioState[1] ? true : false;
  char payload[256];
  data.printTo(payload, sizeof(payload));
  String strPayload = String(payload);
  Serial.print("Get gpio status: ");
  Serial.println(strPayload);
  return strPayload;
}

void set_gpio_status(int pin, boolean enabled) {
  if (pin == GPIO0_PIN) {
    // Output GPIOs state
    digitalWrite(GPIO0, enabled ? HIGH : LOW);
    // Update GPIOs state
    gpioState[0] = enabled;
  } else if (pin == GPIO2_PIN) {
    // Output GPIOs state
    digitalWrite(GPIO2, enabled ? HIGH : LOW);
    // Update GPIOs state
    gpioState[1] = enabled;
  }
}

void InitWiFi() {
  Serial.println("Connecting to AP ...");
  // attempt to connect to WiFi network

  WiFi.begin(WIFI_AP, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(300);
    Serial.print(".");
    digitalWrite(GPIO0, LOW);
    delay(200);
    digitalWrite(GPIO0, HIGH);
  }
  Serial.println("Connected to AP");
  digitalWrite(GPIO0, LOW);
  delay(500);
  digitalWrite(GPIO0, HIGH);
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    status = WiFi.status();
    if ( status != WL_CONNECTED) {
      WiFi.begin(WIFI_AP, WIFI_PASSWORD);
      while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
      }
      Serial.println("Connected to AP");
    }
    Serial.print("Connecting to ThingsBoard node ...");
    // Attempt to connect (clientId, username, password)
    if ( client.connect("ESP8266 Device", TOKEN, NULL) ) {
      Serial.println( "[DONE]" );
      // Subscribing to receive RPC requests
      client.subscribe("v1/devices/me/rpc/request/+");
      // Sending current GPIO status
      Serial.println("Sending current GPIO status ...");
      client.publish("v1/devices/me/attributes", get_gpio_status().c_str());
    } else {
      Serial.print( "[FAILED] [ rc = " );
      Serial.print( client.state() );
      Serial.println( " : retrying in 5 seconds]" );
      // Wait 5 seconds before retrying
      delay( 5000 );
    }
  }
}
arkhipenko commented 5 years ago

I wonder if this version connects within 10 seconds because I don't give the WiFi stack enough time to do the connection work. Here you give it 300 - 500 ms via delay, while I am not using delay at all!

TD-er commented 5 years ago

@arkhipenko You should always give the background processes some time to do stuff. You may also consider adding delay(0) to the code.
In most cases this will not add any noticeable delay to the execution of the code, but when there is something to be done, it will be done. N.B. do not call delay in callback functions.

After calling a connect to wifi it is best to perform a delay of about 103 msec, since the beacon interval is in most cases 102.4 msec, so there is almost no chance you will then miss it. Also on some boards (often cheap ones) you may see that the success rate of connecting increases when the ESP is not doing anything else while trying to connect to WiFi.

arkhipenko commented 5 years ago

Thank you! That's a good point I am doing yield():

      WiFi.disconnect(true);
      yield();                                        // This is an esp8266 standard yield to allow linux wifi stack run
      WiFi.hostname("esp8266");
      WiFi.mode(WIFI_STA);
      WiFi.begin(ssid, pwd);
      yield();

but it obviously not enough. Probably WiFi connection related tasks do need to run as a separate dedicated section, and cooperative scheduling in earnest should start only after all connections are established.

TD-er commented 5 years ago

In ESPeasy we use also some kind of scheduler I wrote myself. (before I saw this project) In this scheduler I call delay(0) at least once before executing things to be done.

But still, there are some reports where some nodes are not able to make a connection while there are tasks to be done during the connection phase. And it is quite specific which node never has issues and which one has, so it seems to be somewhat hardware related. (maybe power related, or some chip revision... I have no idea)

arkhipenko commented 5 years ago

Well, the example above is compiled with #define _TASK_SLEEP_ON_IDLE_RUN statement, which means that Scheduler itself should call delay(1) every time there are no tasks to schedule (which is this case). See here.

Since connection checking happens only once per second, delay(1) should be called close to 1000 times!