Arduino HTML Server Hardware Control

Hi everyone,

I am making something for fun at home, and I'm hitting a bit of a wall trying to handle web server stuff. I am using a Mega2560 as the server with an Ethernet 2 Shield. I have it connected to a relay board that operates buttons on a window unit air conditioner. I also have a temp sensor tied to the Mega just for observation. I have gotten fairly far (made sure the temp sensor reads and works...made sure that my operation of the relay board is correct and it functions), but I'm not able to get variable changes on HTML button presses.

I may be completely approaching this wrong as I am not usually doing web apps, but I would appreciate any and all guidance I could get to make this work. If I can just get one button to work, I can complete the rest of the code, I'm sure. I will post my code below for all who may be interested.

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

uint32_t start;
uint32_t stop;

String readString;

// Temp sensor declaration:
SHT31 sht;

// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
IPAddress ip = {192, 168, 1, 101};

// Initialize the Ethernet server library
// with the IP address and port you want to use
// (port 80 is default for HTTP):
EthernetServer server(80);

void setup() {
  // Open serial communications 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("A/C WebServer");

  // start the Ethernet connection and the server:
  Ethernet.begin(mac, ip);

  // Check for Ethernet hardware present
  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
    Serial.println("Ethernet shield not found");
    while (true) {
      delay(1); // do nothing, no point running without Ethernet hardware
    }
  }
  if (Ethernet.linkStatus() == LinkOFF) {
    Serial.println("Ethernet cable not connected or web client closed");
  }

  // start the server
  server.begin();
  Serial.print("server is at ");
  Serial.println(Ethernet.localIP());

  // assign pins to relay control
  pinMode(30,OUTPUT);
  pinMode(32,OUTPUT);
  pinMode(34,OUTPUT);
  pinMode(36,OUTPUT);
  pinMode(38,OUTPUT);
  pinMode(40,OUTPUT);
  pinMode(42,OUTPUT);
  pinMode(44,OUTPUT);

  // assign pins to detect LEDs
  pinMode (31,INPUT);
  pinMode (33,INPUT);
  pinMode (35,INPUT);
  pinMode (37,INPUT);
  pinMode (39,INPUT);
  pinMode (41,INPUT);

  // temp sensor I2C address
  sht.begin(0x44);

  //clock poll speed for I2C
  Wire.setClock(100000);
  
  uint16_t stat = sht.readStatus();
}

void loop() 
{
  sht.read();
  // listen for incoming clients
  EthernetClient client = server.available();
  if (client) {
    Serial.println("new client");
    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        if (readString.length() < 100) {
          readString += c;
        }
        if (readString.indexOf("power_toggle") > 0) {
          digitalWrite(50,HIGH);
          delay(50);
          digitalWrite(50,LOW);
          delay(50);
        }
        // if you've gotten to the end of the line (received a newline
        // character) and the line is blank, the http request has ended,
        // so you can send a reply
        if (c == '\n' && currentLineIsBlank) {
          Serial.println(readString);
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");  // the connection will be closed after completion of the response
          client.println("Refresh: 5");  // refresh the page automatically every 5 sec
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
            client.println("<form action=\"http://192.168.1.101/\" method=\"get\" id=\"form1\">");
              client.println("<label for=\"shelterTemp\">Current Shelter Temp: </label>");
              // output the value of the temp sensor
              client.println(sht.getTemperature()*1.8+32, 1);
              // temp degree symbol and units (Fahrenheit)
              client.println("\xB0""F<br>");
              //client.println("<button name=power_toggle type=submit value=on>Power</button>");
              client.println("<button name=\"power_toggle\" type=\"submit\" form=\"form1\" value=\"on\">Power</button>");
            client.println("</form>");
          client.println("</html>");
          break;
        }
        if (c == '\n') {
          // you're starting a new line
          currentLineIsBlank = true;
        } else if (c != '\r') {
          // you've gotten a character on the current line
          currentLineIsBlank = false;
        }
      }
    }
    // give the web browser time to receive the data
    delay(1);
    // close the connection:
    client.stop();
    Serial.println("client disconnected");
  }
}

I think I may have found three potential issues in your code that is causing this not to work

I'm assuming you are referring to this part of the code that is not working:

Problem 1:
After using readString, you have no way of clearing its contents. That means that on the next HTML request, readString still contains the previous request's contents and cannot take in any more data

To fix this, you can simply have String readString = ""; declared locally right after a new client appears. Like this:

if (client) {
    Serial.println("new client");
    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    String readString = "";

    ...
}

Problem 2:
This code right here:

if (readString.indexOf("power_toggle") > 0) {
          digitalWrite(50,HIGH);
          delay(50);
          digitalWrite(50,LOW);
          delay(50);
        }

will run over and over many times if "power_toggle" exists in readString and the Arduino is still collecting new data from the request.

To fix this, I would move that entire section after if (c == '\n' && currentLineIsBlank) so it only runs once after the request is read, like this:

        if (c == '\n' && currentLineIsBlank) {
          Serial.println(readString);
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");  // the connection will be closed after completion of the response
          client.println("Refresh: 5");  // refresh the page automatically every 5 sec
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
            client.println("<form action=\"http://192.168.1.101/\" method=\"get\" id=\"form1\">");
              client.println("<label for=\"shelterTemp\">Current Shelter Temp: </label>");
              // output the value of the temp sensor
              client.println(sht.getTemperature()*1.8+32, 1);
              // temp degree symbol and units (Fahrenheit)
              client.println("\xB0""F<br>");
              //client.println("<button name=power_toggle type=submit value=on>Power</button>");
              client.println("<button name=\"power_toggle\" type=\"submit\" form=\"form1\" value=\"on\">Power</button>");
            client.println("</form>");
          client.println("</html>");

          /*
             It is highly recommended not to use delay, especially in this situation
             as the client will possibly have to wait up to 100ms for the connection to be finally closed

             I recommend setting some sort of global variable like "bool powerToggle;" and then 
             toggling the power during the "loop()"
             see: https://docs.arduino.cc/built-in-examples/digital/BlinkWithoutDelay
          */
          if (readString.indexOf("power_toggle") != -1) {
            digitalWrite(50,HIGH);
            delay(50);
            digitalWrite(50,LOW);
            delay(50);
          }
          break;
        }

Problem 3 (Minor Problem):
This piece of code: readString.indexOf("power_toggle") returns anything other than -1 if "power_toggle" is found in the string.

This means the correct way to check for "power_toggle" would be:
if (readString.indexOf("power_toggle") != -1)
or
if (readString.indexOf("power_toggle") > -1)
or
if (readString.indexOf("power_toggle") >= 0)

Some Advice:
I recommend using this line Serial.println(readString); to figure out wether you are even getting the right requests to toggling the power. It is possible that the request to toggle power never even makes it to the arduino

Ok, that does get me some different behavior. Still no dice, but I think we're moving in the right direction. Now, when I click the "Power" button in the web page, the URL changes to include "?power_toggle", and it DOES show up in the serial monitor via Serial.println(readString), but my loop that checks for "power_toggle" never triggers. I will post the updated code below. I'm also going to include some screenshots for clarity.

If you look at the shot of the serial monitor, I'm printing the client information twice for some reason. I don't know where that whole block with "favicon.ico" is coming from, but I'm thinking that the problem is because that block is replacing my readString block.

image

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

uint32_t start;
uint32_t stop;

String readString;

// Temp sensor declaration:
SHT31 sht;

// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
IPAddress ip = {192, 168, 1, 101};

// Initialize the Ethernet server library
// with the IP address and port you want to use
// (port 80 is default for HTTP):
EthernetServer server(80);

void setup() {
  // Open serial communications 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("A/C WebServer");

  // start the Ethernet connection and the server:
  Ethernet.begin(mac, ip);

  // Check for Ethernet hardware present
  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
    Serial.println("Ethernet shield not found");
    while (true) {
      delay(1); // do nothing, no point running without Ethernet hardware
    }
  }
  if (Ethernet.linkStatus() == LinkOFF) {
    Serial.println("Ethernet cable not connected or web client closed");
  }

  // start the server
  server.begin();
  Serial.print("server is at ");
  Serial.println(Ethernet.localIP());

  // assign pins to relay control
  pinMode(30,OUTPUT);
  pinMode(32,OUTPUT);
  pinMode(34,OUTPUT);
  pinMode(36,OUTPUT);
  pinMode(38,OUTPUT);
  pinMode(40,OUTPUT);
  pinMode(42,OUTPUT);
  pinMode(44,OUTPUT);

  // assign pins to detect LEDs
  pinMode (31,INPUT);
  pinMode (33,INPUT);
  pinMode (35,INPUT);
  pinMode (37,INPUT);
  pinMode (39,INPUT);
  pinMode (41,INPUT);

  // temp sensor I2C address
  sht.begin(0x44);

  //clock poll speed for I2C
  Wire.setClock(100000);
  
  uint16_t stat = sht.readStatus();
}

void loop() 
{
  sht.read();
  // listen for incoming clients
  EthernetClient client = server.available();
  if (client) {
    Serial.println("new client");
    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    String readString = "";
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        if (readString.length() < 100) {
          readString += c;
        }
        // if you've gotten to the end of the line (received a newline
        // character) and the line is blank, the http request has ended,
        // so you can send a reply
        if (c == '\n' && currentLineIsBlank) {
          Serial.println(readString);
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");  // the connection will be closed after completion of the response
          client.println("Refresh: 5");  // refresh the page automatically every 5 sec
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
            client.println("<form action=\"http://192.168.1.101/\" method=\"get\" id=\"form1\">");
              client.println("<label for=\"shelterTemp\">Current Shelter Temp: </label>");
              // output the value of the temp sensor
              client.println(sht.getTemperature()*1.8+32, 1);
              // temp degree symbol and units (Fahrenheit)
              client.println("\xB0""F<br>");
              //client.println("<button name=power_toggle type=submit value=on>Power</button>");
              client.println("<button name=\"power_toggle\" type=\"submit\" form=\"form1\" value=\"on\">Power</button>");
            client.println("</form>");
          client.println("</html>");
          if (readString.indexOf("power_toggle") != -1) {
          digitalWrite(50,HIGH);
          delay(50);
          digitalWrite(50,LOW);
          delay(50);
        }
          break;
        }
        if (c == '\n') {
          // you're starting a new line
          currentLineIsBlank = true;
        } else if (c != '\r') {
          // you've gotten a character on the current line
          currentLineIsBlank = false;
        }
      }
    }
    // give the web browser time to receive the data
    delay(1);
    // close the connection:
    client.stop();
    Serial.println("client disconnected");
  }
}

Also, I want to add an extra note here that while the button has to change the variable when it is clicked, it has to go back to its original state after the loop executes. The power_toggle in the URL needs to go away, otherwise it is going to read that every time and execute the loop.

That is really weird. Try to add a print statement inside the if (readString.indexOf("power_toggle") != -1) statement. Like this:

if (readString.indexOf("power_toggle") != -1) {
    Serial.println("Power Toggled");
    digitalWrite(50,HIGH);
    delay(50);
    digitalWrite(50,LOW);
    delay(50);
}

I wonder if we might be dealing with some faulty hardware or something along those lines. It is possible that 50 ms is way too short of a time period to fully restart the AC (caps might need time to drain)

I would also get rid of String readString; at the top of your code. Its an extra declaration that could get in the way

I remember having this problem when I was building a simple web server of my own.

The solution I ended up using was probably not the simplest but I had no other ideas.

I would randomly assign an 'ID' of sorts to the name of the form(action=\"aFormId\") every time the Arduino sent out a new form to a client
Every time the Arduino would receive a submitted form, It would check the form ID if it has been used previously. It will only read each ID once. If the user was to reload the page with that URL again, the Arduino would reject that form as it has an already used/expired ID

This method, however, would require you to parse the name/id from the form also, making it slightly more complicated. Let me know if you want me to provide you with an example of how I got it to work :wink:

I believe there is a much simpler way to get this done without using the URL. If you use the POST method instead of GET in your HTML form(method=\"get\"), its supposed to send you a separate header and body message to the arduino with the form details. The only problem is, I was never able to receive this header and body message on the arduino. I am pretty sure there is a way to do it, I just haven't discovered it yet :frowning:
https://www.xul.fr/javascript/get-post.php

your browser requests that favicon.ico and your code doesn't handle what your browser is requesting.

I have to examples for you:

a) an improved webserver which handles what the browser is sending:

b) some words about the favicon and alternatives how to handle it:
https://werner.rothschopf.net/202010_arduino_webserver_favicon_en.htm

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