Ethernet web server unreachable after some time

So, I have a web server running perfectly fine on the Arduino Uno. What it is supposed to do is turning the PC of and on based on the button clicked on the web server and the status of the PC. I am running a server for friends to have a 24/7 accessible game server. But as it is not used 24/7 i thought, it would be nice to remotely be able to turn the server on and off. And this works all fine, my friends can do this from the internet. But after some time 30+(?) minutes the Arduino web server becomes unreachable and the browser displays a timeout. It is also not responding to pinging it. I have no idea how to solve this issue as i am entirely new to this kind of programming. Any help would be appreciated.

Hardware: Ethernedshield rev 3 with w5100 chip

the code:

#include <Ethernet.h>
#include <SPI.h>

// MAC address from Ethernet shield sticker under board
byte mac[] = {0x28, 0xC3, 0x2E, 0x94, 0x2C, 0xAC};
IPAddress ip(192, 168, 2, 22);
EthernetServer server(48809);   // create a server at port 80

String HTTP_req;          // stores the HTTP request
boolean power_Status = 0;   // state of LED, off by default
int power_pin = 7;

void setup(){
  Serial.begin(9600); // Open serial communications and wait for port to open:
  Ethernet.begin(mac, ip); // start the Ethernet connection:
  if (Ethernet.hardwareStatus() == EthernetNoHardware) { // Check for Ethernet hardware present
    Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    while (true) {
      delay(1); // do nothing, no point running without Ethernet hardware
    }
  }
  
  server.begin(); // start server and print local IP address is test:
  Serial.print("My IP address: ");
  Serial.println(Ethernet.localIP());

  pinMode(power_pin, OUTPUT);
  digitalWrite(power_pin, HIGH);
}

void loop(){
  EthernetClient client = server.available();  // try to get client
  if (client) {
    boolean currentLineIsBlank = true;
    
    while (client.connected()) {
      if (client.available()) {   // client data available to read
          char c = client.read(); // read 1 byte (character) from client
          HTTP_req += c;  // save the HTTP request 1 char at a time
          // last line of client request is blank and ends with \n
          // respond to client only after last line received

        if (c == '\n' && currentLineIsBlank) {
          Serial.print("power status is: ");
          Serial.println(power_Status);
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");
          client.println();
          // send web page
          client.println("<!DOCTYPE html>");
          client.println("<html>");
          client.println("<head>");
          client.println("<title>Power switch website</title>");
          client.println("</head>");
          client.println("<body>");
          client.println("<h1>Server</h1>");
          client.println("<p>Click to switch the server on and off.</p>");
          client.println("<form method=\"get\">");
          checkServer(client);
          ServerStatus(client);
          ProcessCheckbox(client);
          client.println("</form>");

          client.println("</body>");
          client.println("</html>");
          HTTP_req = "";    // finished with request, empty string
          break;
        }
            
        if (c == '\n') {
          // last character on line of received text
          // starting new line with next character read
          currentLineIsBlank = true;
        } 
        else if (c != '\r') {
          // a text character was received from client
          currentLineIsBlank = false;
        }
      }
    }
    delay(1);      // give the web browser time to receive the data
    client.stop(); // close the connection
  }
}

void checkServer(EthernetClient cl){
  Serial.println(HTTP_req);
  Serial.println(HTTP_req.indexOf("?"));
  if (HTTP_req.indexOf("?") > -1) {  // see if checkbox was clicked
    // the checkbox was clicked, toggle the LED
    if (power_Status) {
        power_Status = 0;
    }
    else {
        power_Status = 1;
    }
  }
}

void ServerStatus(EthernetClient cl){
  if(power_Status){
    cl.println("<p>Server is powered :)</p>");
  }else{
    cl.println("<p>Server is dead :(</p>");
  }
}

// switch LED and send back HTML for LED checkbox
void ProcessCheckbox(EthernetClient cl){
  if(HTTP_req.indexOf("?") > -1){
    if (power_Status) {    // switch LED on
      digitalWrite(power_pin, LOW);
      delay(250);
      digitalWrite(power_pin, HIGH);
      
      // checkbox is checked
      cl.println("<input type=\"button\" name=\"LED2\" value=\"Power off\" onclick=\"submit();\">");
      cl.println("<p> server is booting, give it some time</p>");
    }
    else {              // switch LED off
      digitalWrite(power_pin, LOW);
      delay(250);
      digitalWrite(power_pin, HIGH);
      // checkbox is unchecked
      cl.println("<input type=\"button\" name=\"LED2\" value=\"Power on\" onclick=\"submit();\">");
      cl.println("<p>Server is killed</p>");
    }
  }
  if(HTTP_req.indexOf("?") == -1){
    if (power_Status) {    // switch LED on
      cl.println("<input type=\"button\" name=\"LED2\" value=\"Power off\" onclick=\"submit();\">");
    }
    else {
      cl.println("<input type=\"button\" name=\"LED2\" value=\"Power on\" onclick=\"submit();\">");
    }
  }
}

You are using the "String" class which is known to cause problems on small MCU's.

But at the end of the connection I empty the string, that should not keep the data right.
But what do you suggest else other then the string?

It does not matter. Each time the String grows, it reserves a new buffer and releases the old one. This will create memory fragmentation which eventually becomes an issue and your problem is 100% related to that. This line is dangerous: HTTP_req += c; and HTTP_req = ""; does not release the memory, instead the length of the String is nulled. In setup you may have luck pre-reserving a buffer for the String which is large enough to hold the required data.

void setup()
{
  HTTP_req.reserve(200);
  ...
}

All right I'll try that. Do you think the 200 is big enough or was that an arbitrary number?

Only you can tell what the right buffer size is. However, I would encourage you to not read the entire request and deal with it line per line, that would reduce the memory requirement by a lot.

EDIT: And by the way.. You have a lot of inline string constants with HTML, you might want to put those in progmem to reduce memory consumption even further.

And you do that by using the F() macro on all those string constants everywhere in your code

Serial.print(F("My IP address: "));

okay so far no luck. I have implemented the F() macro on each HTML string. And setup a string buffer in the setup. But it was not reachable after about 45 minutes. I also though that using the serial.print might be an issue as the Arduino would be with out USB connection, and so removed the print lines. But that didn't help either.

@Danois90 You said in one of the first messages that "String" is prone to to go bad. Are there alternatives? And I am a bit confused about the:

part. What do you mean read it line by line?

You would use char arrays (aka cstring's) instead of the "String" thing. As far as i can see, you read the entire request before you process it. The request may be quite large and contain lots of unused info about user agent and other stuff, and you do not need to process that. To me it seems like everything you are looking for in the request is whether there is a "?" in it and that could be done much simpler.

void read_request(EthernetClient &client)
{
  #define BUFFER_SIZE 200
  uint8_t buf_idx = 0;
  char buffer[BUFFER_SIZE];
  while (client.connected())
  {
    if (client.available())
    {
      char c = client.read();
      if (c == '\n')
      {
        //Check for empty line which end the request
        if (buf_idx == 0) return;

        buffer[buf_idx] = 0; //Null terminate the line

        //Do something with the line

        buf_idx = 0; //Start new line
      }
      else if (c != '\r')
      {
        if (buf_idx == BUFFER_SIZE - 2) { //ERROR: Buffer is full, this is must be handled! }
        buffer[buf_idx++] = c;
      }
    }
  }
}

The code is not tested, but roughly illustrates how you would read line per line and process on the go..

Thnx for the lines of code and the explanation. So I have tried today to implement it your way. Because you are right, i only have to act on the GET / HTTP1.1 line. But the working wasn't any different as to what it did before. And it made it way much harder to only show the page once instead of all the time a new line came in.

Back to the request I get. It is either GET / or GET /?. If the page is first being visited i receive the GET / HTTP1.1. And if the button on the site is pressed, I get the GET /? HTTP1.1. The rest of the lines like the Host:, User-Agent, etc, are not needed. But I get them anyway. Because if I close the connection before receiving the other lines, the browser thinks the lines are not received and retries to sent the request multiple times. Leading to unwanted behavior by my code.

So based on your last message and the other one before. I got the sense that my program could be running out of memory. And so I did some digging on the web to see what and how the memory of the Arduino works. And I found this test to see if the SRAM is running out but it stays the same after each visit. So that would suggest that I am not running out of RAM. And won't need to read every line separately.

To rule out that it is not the arduino that is bugging out. I replaced the uno with a mega that I had laying around. I'll update if that works any different.

Everything after the "?" is the query for the request as a list of URI encoded name=value pairs separated by "&". If you only want to look for a "?" in the request, you could do something like this:

bool request_has_query(EthernetClient &client)
{
  bool query = false;
  uint16_t line_len = 0;
  while (client.connected())
  {
    if (client.available())
    {
      char c = client.read();
      if (c == '\n')
      {
        if (line_len == 0) return query; //Empty line = end of request
        line_len = 0; //Start new line
      }
      else if (c != '\r')
      {
        line_len++; //Increment line length
        if (c == '?') query = true;
      }
    }
  }
  return query;
}

This approach uses next to no memory, no buffers, and it reads the entire request and returns true if "?" was part of the request.

Thnx for the help. What you suggested seems indeed what I need. However I also noticed that using this way of working with the URL, seems a bit tricky and unpredictable. Because what if someone unintentionally requests the wrong URL and does not want to flip the power status of the PC. So I did some more digging based on your explanation and altered my old code(with the string.reserve) to work the way i want. So after some testing it all seems to work as intended in my test environment.

If I where you I would extract the query string (first line received, from "?" to " ") and examine the content for the right values instead of just checking if a query is present or not - that would be much safer.

Another thing; In your HTML you have onclick="submit();" in the input's but you do not have any javascript attached and that could be a problem. Not a problem, submit() in this case will invoke the forms submit function.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.