Excellent Arduino Ethernet Shield Web Server Tutorial

You asked the question. I answered it. I can lock up any server example except mine. Change the server variable to the target ip you want to lock up. Mine was 192.168.1.254. With my code, it doesn't lock up. With any other example...YOU ARE DOOMED!!

edit: The reason is most Arduino server examples wait for a "\r\n" to determine End Of Header. My crash code sends a request with NO "\r\n". All other server code will lock up unless it receives a '\n'. I don't send one with the crash code. All other examples will wait FOREVER for a '\n'.

Just curious, what hang protection does Apache use?

zoomkat:
Just curious, what hang protection does Apache use?

About the same as mine.
http://playground.arduino.cc/Code/WebServerST

Both IIS and Apache will brush off that lock code without a problem. Only examples here on the Arduino site have problems with that code. Most (except yours zoomkat) can be locked with PuTTY about the same way. Send the GET line and don't send the blank line, then break the connection.

oric_dan:
Thanks. I got one serial monitor prompt and nothing at all after that. And nothing showed up at either URL on my PC [at 192.168.1.177 (my 'static ip') or 192.168.1.254].

I guess I'm too naive and confused to even understand what you're trying to do here. It seems to me you've established router connection at one IPaddress, but are trying to access the server on another,

IPAddress ip(192,168,2,2);

IPAddress server(192,168,1,254);




Hopelessly lost, so probably best to forget my question, :-(.

The "IPAddress ip" is the ip of my Arduino that will do the lock attack. The "IPAddress server" is the ip of the victim Arduino running the server code. Mine are on different subnets.

When you start the lock sketch, you should get "Ready. Enter l to lock" on the serial monitor. Press 'l' and enter. That is a lower case L. Then disconnect the CAT5 cable from the attacking Arduino when prompted.

If you do not have two Arduinos, you can use PuTTY in your computer as the attacker. Enter the ip of the victim Arduino in the "Connect To:" box and port 80 in the "Port" box and select "Raw" as the connection type. Then click "Open" at the bottom. You should get a prompt with no response. Type "GET / HTTP/1.1" and press the enter key only once, then pull the CAT5 out of the computer's network card or out of the Arduino. Either will break the connection. It will lock at that point and will not respond until you reboot it.

PuTTY does not lock up some of zoomkat's server code. PuTTY sends a newline character when you press the enter key and zoomkat's code waits only for the first newline, not the blank line.

The "IPAddress ip" is the ip of my Arduino that will do the lock attack. The "IPAddress server" is the ip of the victim Arduino running the server code. Mine are on different subnets.

Well, that explains a lot, LOL - at this point, I'm happy to have anything working at all.

Ok, I added your timeout counter code from the WebServerST example to my AMI-1284 Server from the other thread. However, PUTTY won't crash anything for me.

With PUTTY, I followed your setup procedure. Then, no matter what I send, , GET, or whatever, my server dumps the web page correctly, PUTTY receives it correctly, and then a window pops up and says "Connection closed by remote host". The page is correct, since I can capture it, stick it in a file with .htm extension using Notepad, and it displays perfectly on the browser.

GET / HTTP/1.1
HTTP/1.1 200 OK
Content-Type: text/html

AMI-1284 Server4
AMI-1284 Server
LED1 (Red)
LED2 (Yellow)
LED3 (Green)
Beep Piezo
Night Light
Fan Ceiling

Vin = 7.61V

Digital Pins D31..D24D23..D16D15..D8D7..D0
00001101aaa000000000111000110011
Analog PinsA5 (D21)A6 (D22)A7 (D23)
293 0 1023

This all happens instantly, so there is no hang at the server, no 10-sec timeout in the server-client loop, and no time to worry about pulling the CAT5 plug. I looked in all the configuration screens of PUTTY, but couldn't see anything obvious to change. "Implicit LF in every CR", and the converse, are not checked.

I'm using my adaptation of zoomkat's logical scheme from the other thread [ie wait for '\n' to dump the page], rather than your WebServerST logical scheme [ie, basically same as original WebServer, except with the timeout added]. The timeout code is in my sketch, in any case.

Thanks, between you and z-k, I seem to be making some progress, :-). I think I can live with what I have.

If you use my timeout code with your Arduino server, you can't crash it with the lockup script or PuTTY. PuTTY won't crash zoomkat's code either. Which code are you using?

I'm using z-k's version with your timeout. I'll experiment some more tomorrow with WebServer style code and no timeout, and z-k's with no timeout. Computer is off, heading for bed now.

This is the part that causes the lockup in most examples. This requires two newlines in a row. You can crash this with PuTTY.

if (c == '\n' && currentLineIsBlank) {

This is what zoomkat uses. PuTTY sends that newline immediately with the request. I could not find a way to stop PuTTY from sending the newline with the GET, so I couldn't crash this with PuTTY.

if (c == '\n') {

edit: You must use the Arduino sketch code above to crash zoomkat's code. It never sends a newline with the GET. Here is the send with no newline from that code. Note it uses client.print() instead of client.println().

  if(client.connect(ipBuf,80))
  {
    Serial.print("connected");

    // send request with no \r\n
    client.print("GET / HTTP/1.1");
  }

Yeah, that's the difference. Tomorrow.

The only disadvantage to zoomkat's code is it does not read the entire request header, only the first line with the GET or POST. Since it doesn't read the rest of the header or the body of the request, you can't use a POST request with zoomkat's code.

Tim, I did some more tests today using your PUTTY scheme.

To confirm your results, for the WebServerST sketch, the server will hang if the timeout counter is disabled, but will timeout and recover if it's enabled. So, that's very good.

Also, as you know, zoomkat's scheme will pass the PUTTY test, as will my modification of it. But I'll leave the timeout code in there, in any case.

The only reservation I have about your/zoomkat's code is it does leave characters in the w5100 socket rx buffer. I have lost many sockets doing that. My server code empties the rx buffer (including any POST data in the request body) before sending a response. That pretty much insures a clean close without losing a socket. But that just me... :smiley:

Thanks, knowledge is king. Now to learn about sockets, :-).

The below meta refresh test code is for checking the turnaround time for a client/server interaction. The server code doesn't seem prone to hanging up on its own from what I can tell. The code has a counter to count the interactions.

// zoomkat's meta refresh data frame test page 8/17/13
// use http://192.168.1.102:84 in your brouser for main page
// http://192.168.1.102:84/data static data page
// http://192.168.1.102:84/datastart meta refresh data page
// for use with W5100 based ethernet shields
// set the refresh rate to 0 for fastest update
// use STOP for single data updates

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

const int analogInPin0 = A0;
const int analogInPin1 = A1;
const int analogInPin2 = A2;
const int analogInPin3 = A3;
const int analogInPin4 = A4;
const int analogInPin5 = A5;

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; //physical mac address
byte ip[] = { 192, 168, 1, 102 }; // arduino ip in lan
byte gateway[] = { 192, 168, 1, 1 }; // internet access via router
byte subnet[] = { 255, 255, 255, 0 }; //subnet mask
EthernetServer server(84); //server port
unsigned long int x=0; //set refresh counter to 0
String readString; 

//////////////////////

void setup(){
  Serial.begin(9600);
    // disable SD SPI if memory card in the uSD slot
  pinMode(4,OUTPUT);
  digitalWrite(4,HIGH);

  Ethernet.begin(mac, ip, gateway, gateway, subnet);
  server.begin();
  Serial.println("meta refresh data frame test 8/17/13"); // so I can keep track of what is loaded
}

void loop(){
  EthernetClient client = server.available();
  if (client) {
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
         if (readString.length() < 100) {
          readString += c; 
         } 
        //check if HTTP request has ended
        if (c == '\n') {

          //check get atring received
          Serial.println(readString);

          //output HTML data header
          //client.println("HTTP/1.1 200 OK");
          //client.println("Content-Type: text/html");
          //client.println();
          
          client.print(F("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n"));

          //generate data page
          if(readString.indexOf("data") >0) {  //checks for "data" page
            x=x+1; //page upload counter
            client.print("<HTML><HEAD>");
            //meta-refresh page every 1 seconds if "datastart" page
            if(readString.indexOf("datastart") >0) client.print("<meta http-equiv='refresh' content='1'>"); 
            //meta-refresh 0 for fast data
            if(readString.indexOf("datafast") >0) client.print("<meta http-equiv='refresh' content='0'>"); 
            client.print("<title>Zoomkat's meta-refresh test</title></head><BODY>
");
            client.print("page refresh number: ");
            client.print(x); //current refresh count
            client.print("

");
            
              //output the value of each analog input pin
            client.print("analog input0 is: ");
            client.print(analogRead(analogInPin0));
            
            client.print("
analog input1 is: ");
            client.print(analogRead(analogInPin1));
                        
            client.print("
analog input2 is: ");
            client.print(analogRead(analogInPin2));
            
            client.print("
analog input3 is: ");
            client.print(analogRead(analogInPin3));
                                    
            client.print("
analog input4 is: ");
            client.print(analogRead(analogInPin4));
            
            client.print("
analog input5 is: ");
            client.print(analogRead(analogInPin5));
            client.print("
</BODY></HTML>");
           }
          //generate main page with iframe
          else
          {
            client.print(F("<HTML><HEAD><TITLE>Zoomkat's frame refresh test</TITLE></HEAD>"
            "Zoomkat's Arduino frame meta refresh test 8/17/13"
            "

Arduino analog input data frame:
"
            "&nbsp;&nbsp;<a href='/datastart' target='DataBox' title=''yy''>META-REFRESH</a>"
            "&nbsp;&nbsp;&nbsp;&nbsp;<a href='/data' target='DataBox' title=''xx''>SINGLE-STOP</a>"
            "&nbsp;&nbsp;&nbsp;&nbsp;<a href='/datafast' target='DataBox' title=''zz''>FAST-DATA</a>
"
            "<iframe src='/data' width='350' height='250' name='DataBox'>"
            "</iframe>
</HTML>"));
          }
          delay(1);
          //stopping client
          client.stop();
          //clearing string for next read
          readString="";
        }
      }
    }
  }
}

I wrote a socket test sketch a few months ago. It is a web server, plus does a dns request for Google and ntp request every 30 seconds. It shows the socket status at different points during each. It shows for each socket
Socket#: D:(remote port)

The common status byte codes are
0x00 = available (not used)
0x14 = server waiting for client
0x17 = server with client
0x22 = udp

Insure network settings are correct, including a valid dns server.

#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
#include <utility/w5100.h>
#include <Dns.h>

byte mac[] = {  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,2,2);
IPAddress gateway(192,168,2, 1);
IPAddress subnet(255, 255, 255, 0);
IPAddress dnServer(192,168,2,1);

IPAddress timeServer;
EthernetServer server(80);
EthernetUDP Udp;
DNSClient dnsC;

long timer = 0L;
const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets 

void setup() {
  Serial.begin(9600);

  pinMode(4, OUTPUT);
  digitalWrite(4, HIGH);
        
  Ethernet.begin(mac, ip, dnServer, gateway, subnet);

  Serial.println("Start");
  server.begin();
  ShowSockStatus();
  timer = millis();
  dnsC.begin(dnServer);

  if(dnsC.getHostByName("pool.ntp.org",timeServer)) {
    Serial.print("\r\nNTP address = ");
    Serial.println(timeServer);            
  }  
  else Serial.println("dns fail");
  ShowSockStatus();
}

void loop()
{ 
  IPAddress remoteAddr;
  //Check if a web client has attached.
  checkServer();
        
  if ((millis() - timer) > 30000) {
    if(dnsC.getHostByName("www.google.com",remoteAddr)) {
      Serial.print(F("\r\nIP address = "));
      Serial.println(remoteAddr);            
    }  
    else Serial.println(F("dns fail"));
          
    ShowSockStatus();
    delay(10);          
    timer = millis();
    checkTime();
  }
}

void ShowSockStatus()
{
  for (int i = 0; i < MAX_SOCK_NUM; i++) {
    Serial.print(F("Socket#"));
    Serial.print(i);
    uint8_t s = W5100.readSnSR(i);
    Serial.print(F(":0x"));
    Serial.print(s,16);
    Serial.print(F(" "));
    Serial.print(W5100.readSnPORT(i));
    Serial.print(F(" D:"));
    uint8_t dip[4];
    W5100.readSnDIPR(i, dip);
    for (int j=0; j<4; j++) {
      Serial.print(dip[j],10);
      if (j<3) Serial.print(".");
    }
    Serial.print(F("("));
    Serial.print(W5100.readSnDPORT(i));
    Serial.println(F(")"));
  }
}

unsigned long sendNTPpacket(EthernetUDP & Udp, IPAddress& address)
{
  memset(packetBuffer, 0, NTP_PACKET_SIZE); 
  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
  packetBuffer[12]  = 49; 
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer,NTP_PACKET_SIZE);
  Udp.endPacket(); 
}

void checkTime()
{
  Udp.begin(8888);
  sendNTPpacket(Udp, timeServer); // send an NTP packet to a time server
  Serial.println("\r\nTime check");
  ShowSockStatus();
  // wait to see if a reply is available
  delay(1000);  

  if ( Udp.parsePacket() ) {  
    // 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:
    Serial.print("Unix time = ");
    // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
    const unsigned long seventyYears = 2208988800UL;     
    // subtract seventy years:
    unsigned long epoch = secsSince1900 - seventyYears;  
    // print Unix time:
    Serial.println(epoch);  
  }  
  else Serial.println("No NTP packet received");

  Udp.stop();
  ShowSockStatus();
}

void checkServer()
{
  EthernetClient client = server.available();
  if(client) {
    boolean currentLineIsBlank = true;
    boolean currentLineIsGet = true;
    int tCount = 0;
    char tBuf[64];
    int r,t;
    char *pch;

    Serial.println(F("\r\nServer client"));
    ShowSockStatus();

    Serial.print(F("\r\nClient request: "));

    // this controls the timeout
    int loopCount = 0;

    while (client.connected()) {
      while(client.available()) {
        // if packet, reset loopCount
        loopCount = 0;
        char c = client.read();
        if(currentLineIsGet && tCount < 63)
        {
          tBuf[tCount] = c;
          tCount++;
          tBuf[tCount] = 0;          
        }

        if (c == '\n' && currentLineIsBlank) {
          // send a standard http response
          Serial.println(tBuf);
          Serial.print(F("POST data: "));
          while(client.available()) Serial.write(client.read());
          Serial.println();

          pch = strtok(tBuf,"?");

          while(pch != NULL)
          {
            if(strncmp(pch,"t=",2) == 0)
            {
              t = atoi(pch+2);
              Serial.print(F("t="));
              Serial.println(t,DEC);             
            }

            if(strncmp(pch,"r=",2) == 0)
            {
              r = atoi(pch+2);
              Serial.print(F("r="));              
              Serial.println(r,DEC);
            }


            pch = strtok(NULL,"& ");
          }
          Serial.println(F("Sending response"));
          client.print(F("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n<html>"));
          client.println(F("<body><H1>TEST</H1>"));
          client.println(F("<form method=GET>T: <input type=text name=t>
"));
          client.println(F("R: <input type=text name=r>
<input type=submit></form>"));


          client.println(F("</body></html>"));
          client.stop();
        }
        else if (c == '\n') {
          currentLineIsBlank = true;
          currentLineIsGet = false;
        } 
        else if (c != '\r') {
          currentLineIsBlank = false;
        }
      }

      loopCount++;

      // if 10000ms has passed since last packet
      if(loopCount > 10000) {
        // close connection
        client.stop();
        Serial.println(F("\r\nTimeout"));
      }

      // delay 1ms for timeout timing
      delay(1);
    }
    Serial.println(F("done"));
  }
}

edit: The timeServer ip will be assigned by dns from pool.ntp.org.
The available (0x00) and server waiting (0x14) sockets will show the remote ip and ports settings from the previous use of that socket.

Hi,

I use in my house an Arduino UNO with Ethernet Shield connected to the router that controls the lights of the house through the computer and smartphone by socket commands.

I am now trying to create a mini Webserver by Arduino to control the lights by Smart TV also through the browser, and I’m following the instructions in Part 5 of the tutorial.

I adapted the code from the tutorial with the code that I already used, but I have questions in some parts:

void loop() {

EthernetClient client = server.available();

// SE receber um caracter...

delay(50); 
if (client) {

boolean currentLineIsBlank = true;

// guarda o caracter na string 'msg'
msg[0]=msg[1];
msg[1]=msg[2];
msg[2]=msg[3];
msg[3]=msg[4];
msg[4]= client.read();


char c = msg[4]; // Lê 1 byte (caractere) do cliente
HTTP_req += c;  // Salva o pedido HTTP 1 caractere de cada vez


if (msg[4]=='#')
{
			   
   LOOKS AND EXECUTE SOCKET COMMANDS
   
} // Fim IF msg[4]
else
{
 while (client.connected())
 {
   if (client.available())
   {
	 if (c == '\n' && currentLineIsBlank)
	 {
		// Envia um cabeçalho de resposta padrão HTTP
		client.println("HTTP/1.1 200 OK");
		client.println("Content-Type: text/html");
		client.println("Connection: close");
		client.println();
		// Envia a página
		client.println("<!DOCTYPE html>");
		client.println("<html>");
		client.println("<head>");
		client.println("<title>Arduino - Luzes da Casa</title>");
		client.println("</head>");
		client.println("<body>");
		client.println("<center>");
		client.println("<h1>Luzes da Casa</h1>");
		client.println("<p>Clique para Acender ou Apagar a luz.</p>");
		client.println("<form method=\"get\">");
		ProcessButton(client);
		client.println("</form>");
		client.println("</center>");
		client.println("</body>");
		client.println("</html>");
		Serial.print(HTTP_req);
		HTTP_req = "";    // Termina o pedido, limpa a String
		break;
	 }
	 
	 if (c == '\n')
	 {
	   currentLineIsBlank = true;
	 } 
	 else if (c != '\r')
	 {
	   currentLineIsBlank = false;
	 }
   } // Fim (client.available())
 } // Fim while (client.connected())
 delay(1);      // Dar tempo ao navegador para receber os dados
 client.stop(); // Fecha a conexão
} // Fim ELSE
} // Fim client
} // Fim Loop

// Mudar LUZ e enviar de volta para a caixa HTML
void ProcessButton(EthernetClient cl)
{
if (HTTP_req.indexOf("luz=Quarto") > 0)
{
  if (digitalRead(4) == LOW)
  {
	  digitalWrite(4, HIGH);
  }
  else
  {
	  digitalWrite(4, LOW);
  }
}

if (HTTP_req.indexOf("luz=Sala") > 0)
{
  if (digitalRead(5) == LOW)
  {
	  digitalWrite(5, HIGH);
  }
  else
  {
	  digitalWrite(5, LOW);
  }
}

cl.println F("<input type=\"submit\" value=\"Quarto\" onclick=\"submit();\" name=\"luz\" style=\"height:300px; width:300px; font-size:50px\">");
cl.println F("<input type=\"submit\" value=\"Sala\" onclick=\"submit();\" name=\"luz\" style=\"height:300px; width:300px; font-size:50px\">");
}

When making a socket connection, the Arduino would not be stuck in “While” blocking of analyzing the messages to be sent? And if I delete the “While”, the Arduino would not be resubmitting the page every time it goes by Loop?

And in “client.stop ();”, when making a socket connection, it would also be closed?

If you mean Part 5 of this tutorial, you will notice [as I mentioned a few posts ago], his elements are wrong. He needs either a or " />" to close them, and make them correct [at least I think so]. If you do what he does with multiple controls, and not just one, then they interact and the control logic gets mucked up.

oric_dan:
If you mean Part 5 of this tutorial, you will notice [as I mentioned a few posts ago], his elements are wrong. He needs either a or " />" to close them, and make them correct [at least I think so]. If you do what he does with multiple controls, and not just one, then they interact and the control logic gets mucked up.

LED Control with Arduino Ethernet Shield Web Server

Now I put the " />".

But when making a socket connection, the Arduino would not be stuck in “While” blocking of analyzing the messages to be sent? And if I delete the “While”, the Arduino would not be resubmitting the page every time it goes by Loop?

And in “client.stop ();”, when making a socket connection, it would also be closed?

Tim will have to answer those questions. I'm just learning this stuff, like the " />", :-).

I changed the code, I wonder if will work the Webserver and Socket connection:

...

void loop() {
      
      EthernetClient client = server.available();
    
     // SE receber um caracter...
        
      delay(50); 
      if (client) {
        
        boolean currentLineIsBlank = true;
        while (client.connected())
         {
           if (client.available())
           {
              // guarda o caracter na string 'msg'
              msg[0]=msg[1];
              msg[1]=msg[2];
              msg[2]=msg[3];
              msg[3]=msg[4];
              msg[4]= client.read();
              
              
              char c = client.read(); // Lê 1 byte (caractere) do cliente
              HTTP_req += c;  // Salva o pedido HTTP 1 caractere de cada vez
              
      
             if (msg[4]=='#')
             {

                LOOKS AND EXECUTE SOCKET COMMANDS
        
             } // Fim IF msg[4]
             else
             {
               if (c == '\n' && currentLineIsBlank)
               {
                  // Envia um cabeçalho de resposta padrão HTTP
                  client.println("HTTP/1.1 200 OK");
                  client.println("Content-Type: text/html");
                  client.println("Connection: close");
                  client.println();
                  // Envia a página
                  client.println("<!DOCTYPE html>");
                  client.println("<html>");
                  client.println("<head>");
                  client.println("<title>Arduino - Luzes da Casa</title>");
                  client.println("</head>");
                  client.println("<body>");
                  client.println("<center>");
                  client.println("<h1>Luzes da Casa</h1>");
                  client.println("<p>Clique para Acender ou Apagar a luz.</p>");
                  client.println("<form method=\"get\">");
                  ProcessButton(client);
                  client.println("</form>");
                  client.println("</center>");
                  client.println("</body>");
                  client.println("</html>");
                  Serial.print(HTTP_req);
                  HTTP_req = "";    // Termina o pedido, limpa a String
                  break;
               }
               
               if (c == '\n')
               {
                 currentLineIsBlank = true;
               } 
               else if (c != '\r')
               {
                 currentLineIsBlank = false;
               }
             } // Fim ELSE
           } // Fim (client.available())
        } // Fim while (client.connected())
        
        delay(1);      // Dar tempo ao navegador para receber os dados
        client.stop(); // Fecha a conexão
     } // Fim client
   } // Fim Loop
   
  // Mudar LUZ e enviar de volta para a caixa HTML
  void ProcessButton(EthernetClient cl)
  {
      if (HTTP_req.indexOf("luz=Quarto") > 0)
      {
          if (digitalRead(4) == LOW)
          {
              digitalWrite(4, HIGH);
          }
          else
          {
              digitalWrite(4, LOW);
          }
      }
      
      if (HTTP_req.indexOf("luz=Sala") > 0)
      {
          if (digitalRead(5) == LOW)
          {
              digitalWrite(5, HIGH);
          }
          else
          {
              digitalWrite(5, LOW);
          }
      }
      
      cl.println F("<input type=\"submit\" value=\"Quarto\" onclick=\"submit();\" name=\"luz\" style=\"height:300px; width:300px; font-size:50px\" />");
      cl.println F("<input type=\"submit\" value=\"Sala\" onclick=\"submit();\" name=\"luz\" style=\"height:300px; width:300px; font-size:50px\" />");
  }

...