arduino / ArduinoCore-mbed

330 stars 195 forks source link

GIGAR1: conflict between Access Point and common WiFi connection #858

Open giorgiogallina opened 5 months ago

giorgiogallina commented 5 months ago

Hello,

I am facing some issues while trying, in a larger project, to accomplish the following:

  1. Try to connect to the internet via WiFi
  2. If connection fails, open Access Point (AP) and let user insert WiFi credentials (through captive portal)
  3. Go back to point 1

Every time I close a connection, I call WiFi.disconnect() and WiFi.end(); Moreover, when AP operations are completed, I call WiFiClient::stop(). My WiFiClient object is global and obtained through WiFiServer::available().

After closing AP connection, I could connect to the WiFi but any connection (e.g., to www.google.com) failed by a time-out. I kind of solved the problem through the following code I invoke every time I switch between point 1 and 2 of the algorithm. WiFi = WiFiClass(WiFiInterface::get_default_instance()); However I do not understand such a behaviour.

Now, if WiFi connection fails after AP was accomplished once, AP is opened again, but this time WiFiServer::available() fails, in the sense that no data are ever available. However, I controlled network traffic through Wireshark and I see that DNS management through WiFiUDP succeded, while an http request GET /wpad.dat HTTP/1.1 was sent by the client, but never received by the server.

It seems to me that there are library issues then, or at least poor documentation for me to be able to understand whether I am doing anything wrong

JAndrassy commented 5 months ago

A WiFiClient instance is bind with network after first connect, so it will still try to use AP if it was first used with AP. Why do you use one client object for both?

JAndrassy commented 5 months ago

please try in SocketWrapper library in AClient.cpp in AClient:stop() add client.reset(); as last line

giorgiogallina commented 5 months ago

Dear Juraj, I thank you very much for your quick response. I modified AClient.cpp as you suggested, but with no success. I wrote a sketch as simple as I could, building up on WiFi Access Point example provided in GIGA R1 documentation, for replication of the situation. It seems not exactly the same as the one I am witnessing in my project, yet similar. I stop and open again AP every minute and what I get is that it alternatively succeeds and fails.

/*
  WiFi Web Server LED Blink

  A simple web server that lets you blink an LED via the web.
  This sketch will create a new access point (with no password).
  It will then launch a new server and print out the IP address
  to the Serial Monitor. From there, you can open that address in a web browser
  to turn on and off the LED on pin 13.

  If the IP address of your board is yourAddress:
    http://yourAddress/H turns the LED on
    http://yourAddress/L turns it off

  created 25 Nov 2012
  by Tom Igoe
  adapted to WiFi AP by Adafruit

  modified 22 March 2023
  by Karl Söderby

  Modified 04 April 2024
  by Giorgio Gallina
 */

#include <SPI.h>
#include <WiFi.h>
#include <WiFiServer.h>
#include <WiFiClient.h>
#include <WiFiUdp.h>

char ssid[] = "ArduinoAP";
char pass[] = "Qwerty123";

int status = WL_IDLE_STATUS;

#define UDP_PACKET_SIZE 1024
#define DNSHEADER_SIZE    12
#define DNSANSWER_SIZE    16

WiFiServer* server = new WiFiServer(80);    // Global Acces Point Web Server
WiFiUDP* G_UDPAP_DNS = new WiFiUDP();       // A UDP instance to let us send and receive packets over UDP
IPAddress G_APip;                           // Global Acces Point IP adress
byte G_UDPPacketbuffer[UDP_PACKET_SIZE];  // buffer to hold incoming and outgoing packets
byte G_DNSReplyheader[DNSHEADER_SIZE] = {
  0x00, 0x00,  // ID, to be filled in #offset 0
  0x81, 0x80,  // answer header Codes
  0x00, 0x01,  //QDCOUNT = 1 question
  0x00, 0x01,  //ANCOUNT = 1 answer
  0x00, 0x00,  //NSCOUNT / ignore
  0x00, 0x00   //ARCOUNT / ignore
};
byte G_DNSReplyanswer[DNSANSWER_SIZE] = {
  0xc0, 0x0c,  // pointer to pos 12 : NAME Labels
  0x00, 0x01,  // TYPE
  0x00, 0x01,  // CLASS
  0x00, 0x00,  // TTL
  0x18, 0x4c,  // TLL 2 days
  0x00, 0x04,  // RDLENGTH = 4
  0x00, 0x00,  // IP adress octets to be filled #offset 12
  0x00, 0x00   // IP adress octeds to be filled
};

/* DNS Routines via UDP, act on DSN requests on Port 53*/
/* assume wifi UDP connection has been set up */
bool APDNSScan() {
  int t = 0;  // generic loop counter
  int r, p;   // reply and packet counters
  int flag = 0;
  unsigned int packetSize = 0;
  unsigned int replySize = 0;
  byte G_DNSReplybuffer[UDP_PACKET_SIZE];  // buffer to hold the send DNS reply
  bool dns_flag = true;
  IPAddress G_APDNSclientip;
  int G_DNSClientport;

  packetSize = G_UDPAP_DNS->parsePacket();
  if (packetSize > 1023)
  {
    return false;
  }
  if (packetSize) {                                   // We've received a packet, read the data from it
    int plen = G_UDPAP_DNS->read(G_UDPPacketbuffer, packetSize);  // read the packet into the buffer
    if (plen > 0 && plen < 1024)
    {
      // G_UDPPacketbuffer[plen] = 0;
    }
    G_APDNSclientip = G_UDPAP_DNS->remoteIP();
    G_DNSClientport = G_UDPAP_DNS->remotePort();
    //  if ( (G_APDNSclientip != G_APip) && (G_DNSRqstcounter<=DNSMAXREQUESTS) )       // skip own requests - ie ntp-pool time requestfrom Wifi module
    if ((G_APDNSclientip != G_APip))  // skip own requests - ie ntp-pool time requestfrom Wifi module

    {
      Serial.println("DNS-packets (" + String(packetSize) + ") from " + String(G_APDNSclientip) + " port " + String(G_DNSClientport));
      for (t = 0; t < packetSize; ++t) {
        Serial.print(String(G_UDPPacketbuffer[t], HEX));
        Serial.print(":");
      }
      Serial.println(" ");
      for (t = 0; t < packetSize; ++t) {
        Serial.print(String((char)G_UDPPacketbuffer[t]));  //Serial.print("");
      }
      Serial.println("");

      //Copy Packet ID and IP into DNS header and DNS answer
      G_DNSReplyheader[0] = G_UDPPacketbuffer[0];
      G_DNSReplyheader[1] = G_UDPPacketbuffer[1];  // Copy ID of Packet offset 0 in Header
      G_DNSReplyanswer[12] = G_APip[0];
      G_DNSReplyanswer[13] = G_APip[1];
      G_DNSReplyanswer[14] = G_APip[2];
      G_DNSReplyanswer[15] = G_APip[3];  // copy AP Ip adress offset 12 in Answer
      r = 0;                             // set reply buffer counter
      p = 12;                            // set packetbuffer counter @ QUESTION QNAME section
      // copy Header into reply
      for (t = 0; t < DNSHEADER_SIZE; ++t) G_DNSReplybuffer[r++] = G_DNSReplyheader[t];
      // copy Question into reply:  Name labels till octet=0x00
      while (G_UDPPacketbuffer[p] != 0) G_DNSReplybuffer[r++] = G_UDPPacketbuffer[p++];
      // copy end of question plus Qtype and Qclass 5 octets
      for (t = 0; t < 5; ++t) G_DNSReplybuffer[r++] = G_UDPPacketbuffer[p++];
      //copy Answer into reply
      for (t = 0; t < DNSANSWER_SIZE; ++t) G_DNSReplybuffer[r++] = G_DNSReplyanswer[t];
      replySize = r;

      Serial.println("* DNS-Reply (" + String(replySize) + ") from " + String(G_APip) + " port " + String(53));
      for (t = 0; t < replySize; ++t) {
        Serial.print(String(G_DNSReplybuffer[t], HEX));
        Serial.print(":");
      }
      Serial.println();
      for (t = 0; t < replySize; ++t) {
        Serial.print(String((char)G_DNSReplybuffer[t]));  //Serial.print("");
      }
      Serial.println("\n");

      // Send DSN UDP packet
      dns_flag &= (G_UDPAP_DNS->beginPacket(G_APDNSclientip, G_DNSClientport)  >  0);  //reply DNSquestion
      uint8_t dns_count = 6;
      G_UDPAP_DNS->write(G_DNSReplybuffer, replySize);
      dns_flag &= (G_UDPAP_DNS->endPacket()  >  0);

    }  // end loop correct IP
  }    // end loop received packet

  return dns_flag;
}

void connectAP()
{
  G_APip = IPAddress((char)random(11, 172), (char)random(0, 255), (char)random(0, 255), 0x01);  // Generate random IP adress in Private IP range
  WiFi.config(G_APip, G_APip, G_APip);

  status = WiFi.beginAP(ssid, pass);

  Serial.println("  beginAP  status: " + String(status));
  if (status != WL_AP_LISTENING) {
    Serial.println("Creating access point failed");
    do {
      delay(1000);
      status = WiFi.beginAP(ssid, pass);
      Serial.println("status: " + String(status));
    } while (status != WL_AP_LISTENING);
  }

  G_UDPAP_DNS->begin(53);
}

void setup() {
  //Initialize serial and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ;  // wait for serial port to connect. Needed for native USB port only
  }

  Serial.println("Access Point Web Server");

  pinMode(LED_RED, OUTPUT);
  pinMode(LED_GREEN, OUTPUT);
  pinMode(LED_BLUE, OUTPUT);

  // check for the WiFi module:
  if (WiFi.status() == WL_NO_MODULE) {
    Serial.println("Communication with WiFi module failed!");
    // don't continue
    while (true)
      ;
  }

  // print the network name (SSID);
  Serial.print("Creating access point named: ");
  Serial.println(ssid);

  // Create open network. Change this line if you want to create an WEP network:
  connectAP();

  // wait 6 seconds for connection:
  delay(6000);

  // start the web server on port 80
  server->begin();

  // you're connected now, so print out the status
  printWiFiStatus();
}

void loop() {

  /* RESET CONNECTION AFTER 1m 15s */
  static uint64_t timer = millis();

  if (millis() - timer > 75000)
  {
    Serial.println("\n-----------  RECONNECTING  -----------");

    // clean-up
    G_UDPAP_DNS->stop();
    WiFi.disconnect();
    WiFi.end();
    delay(2000);
    Serial.println("  end()    status: " + String(WiFi.status()));

    // deeper clean-up
    WiFi = * ((WiFiClass*) (new WiFiClass(WiFiInterface::get_default_instance())));
    delay(100);
    Serial.println("  new      status: " + String(WiFi.status()));

    // try to connect to inexistent network
    status = WiFi.begin(ssid, pass);
    Serial.println("  begin()  status: " + String(status));
    delay(500);
    status = WiFi.begin(ssid, pass);
    Serial.println("  begin()  status: " + String(status));
    delay(2000);

    // clean-up
    WiFi.disconnect();
    WiFi.end();
    delay(2000);
    Serial.println("  end()    status: " + String(WiFi.status()));

    //deeper clean-up
    WiFi = * ((WiFiClass*) (new WiFiClass(WiFiInterface::get_default_instance())));
    delay(100);
    Serial.println("  new      status: " + String(WiFi.status()));

    // create Access Point
    connectAP();
    delay(5000);

    Serial.println("  AP ok    status: " + String(WiFi.status()));
    Serial.println("--------------------------------------\n");

    //reset timer
    timer = millis();
  }

  /* -----  DNS REDIRECTION FOR CAPTIVE PORTAL (UDP)  ----- */
  APDNSScan();

  /* ------------  REST COMMUNICATION (TCP?)  ------------- */

  // compare the previous status to the current status
  if (status != WiFi.status()) {
    // it has changed update the variable
    status = WiFi.status();

    if (status == WL_AP_CONNECTED) {
      // a device has connected to the AP
      Serial.println("Device connected to AP");
    } else {
      // a device has disconnected from the AP, and we are back in listening mode
      Serial.println("Device disconnected from AP");
    }
  }

  WiFiClient client = server->available();  // listen for incoming clients

  if (client) {                    // if you get a client,
    Serial.println("new client");  // print a message out the serial port
    String currentLine = "";       // make a String to hold incoming data from the client
    while (client.connected()) {   // loop while the client's connected
      delayMicroseconds(10);       // This is required for the Arduino Nano RP2040 Connect - otherwise it will loop so fast that SPI will never be served.
      if (client.available()) {    // if there's bytes to read from the client,
        char c = client.read();    // read a byte, then
        Serial.write(c);           // print it out the serial monitor
        if (c == '\n') {           // if the byte is a newline character

          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();

            // the content of the HTTP response follows the header:
            client.print("Click <a href=\"/HR\">here</a> turn the RED LED on<br>");
            client.print("Click <a href=\"/LR\">here</a> turn the RED LED off<br>");
            client.print("Click <a href=\"/HG\">here</a> turn the GREEN LED ON<br>");
            client.print("Click <a href=\"/LG\">here</a> turn the GREEN LED off<br>");
            client.print("Click <a href=\"/BH\">here</a> turn the BLUE LED on<br>");
            client.print("Click <a href=\"/BL\">here</a> turn the BLUE LED off<br>");

            // The HTTP response ends with another blank line:
            client.println();
            // break out of the while loop:
            break;
          } else {  // if you got a newline, then clear currentLine:
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }

        // Check to see if the client request (turns ON/OFF the different LEDs)
        if (currentLine.endsWith("GET /HR")) {
          digitalWrite(LED_RED, LOW);  
        }
        if (currentLine.endsWith("GET /LR")) {
          digitalWrite(LED_RED, HIGH); 
        }
        if (currentLine.endsWith("GET /HG")) {
          digitalWrite(LED_GREEN, LOW); 
        }
        if (currentLine.endsWith("GET /LG")) {
          digitalWrite(LED_GREEN, HIGH);  
        }
        if (currentLine.endsWith("GET /BH")) {
          digitalWrite(LED_BLUE, LOW);  
        }
        if (currentLine.endsWith("GET /BL")) {
          digitalWrite(LED_BLUE, HIGH);  
        }
      }
    }
    // close the connection:
    client.stop();
    Serial.println("client disconnected");
  }
}

void printWiFiStatus() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your WiFi shield's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print where to go in a browser:
  // Serial.print("To see this page in action, open a browser to http://");
  // Serial.println(ip);
}
JAndrassy commented 5 months ago

my intuition says it is the server now. try to begin it when the WiFi setup changes.

EDIT: repeated begin will not help. you need end from PR https://github.com/arduino/ArduinoCore-mbed/pull/751

giorgiogallina commented 5 months ago

I supposed so. Indeed, I tried to do:

delete server;
server = new WiFiServer(80);

However, this seems to makes things even worse. Thank you for your help anyway