title: Arduino client + PHP cURL server executing command twice
body: <p>I have this project I'm doing for work in which I command several Arduino (Arduino core + ENC28J60 Ethernet + x4 Relay actuator) modules from a single PHP server to activate a relay on any of the Arduino modules. The server has a list of all the events and executes them as the time is right for each one. What's wrong is that whenever the commands are more than 4 minutes apart (i.e. >=5 minutes) the command gets executed twice by the Arduino. That is, Arduino activates the relay I commanded twice in a row.</p>
  
  <p>What the code does is this:
  1. thor.php is executed lineally once (the task is repeated by a crontab)
  2. thor.php searches within its arrays for an occurrence of an event to take place at the current time
  3. for each occurrence it generates a task that's delivered to the curl multi handler
  4. all the tasks are sent in parallel to each arduino module.
  5. when an Arduino receives a request, checks whether it comes from a known IP address and through the allowed port, analyzes the command in the parameters, and activates the relays as requested.
  6. the Arduino then sends a response page with a hidden field that will work in the future for control.</p>
  
  <p>In theory all works well, but <strong>whenever the commands are 5 minutes or more apart, the Arduino executes the command twice.</strong></p>
  
  <p>I put the whole code next.
  Here's the Arduino: (Pardon the comments in Spanish)</p>
  
  <pre><code>    #include "etherShield.h"
  
      //MAC ADDRESS.
      static uint8_t mymac[6] = {
        0x54,0x55,0x58,0x10,0x00,0x24}; 
      //IP ADDRESS THOR.
      static uint8_t myip[4] = {
        172,0,0,101};
      //Unica IP de Origen aceptada.
      static uint8_t ip_origen[4] = {
        172,0,0,10};
      //TCP PORT
      static uint16_t myport = 5566;
      //Setear los pines de los relays. Solo se setea el primero. Se necesitan 4 pines consecutivos libres
      static int primerrelay = 2;
  
      //Variables globales usadas para el feedbak del modulo en una peticion tcp.
      int16_t comando_rel, comando_tmp;
      //Estado de los relays
      uint8_t estado;
  
      //Definiciones propias de Arduino. Especifica el tamaño maximo del buffer y lo inicializa.
      #define BUFFER_SIZE 500
      static uint8_t buf[BUFFER_SIZE+1];
  
      EtherShield es=EtherShield();
  
      void setup(){
  
        /*initialize enc28j60*/
        es.ES_enc28j60Init(mymac);
        es.ES_enc28j60clkout(2); // change clkout from 6.25MHz to 12.5MHz
        delay(10);
  
        /* Magjack leds configuration, see enc28j60 datasheet, page 11 */
        // LEDA=greed LEDB=yellow
        //
        // 0x880 is PHLCON LEDB=on, LEDA=on
        // enc28j60PhyWrite(PHLCON,0b0000 1000 1000 00 00);
        es.ES_enc28j60PhyWrite(PHLCON,0x880);
        delay(500);
        //
        // 0x990 is PHLCON LEDB=off, LEDA=off
        // enc28j60PhyWrite(PHLCON,0b0000 1001 1001 00 00);
        es.ES_enc28j60PhyWrite(PHLCON,0x990);
        delay(500);
        //
        // 0x880 is PHLCON LEDB=on, LEDA=on
        // enc28j60PhyWrite(PHLCON,0b0000 1000 1000 00 00);
        es.ES_enc28j60PhyWrite(PHLCON,0x880);
        delay(500);
        //
        // 0x990 is PHLCON LEDB=off, LEDA=off
        // enc28j60PhyWrite(PHLCON,0b0000 1001 1001 00 00);
        es.ES_enc28j60PhyWrite(PHLCON,0x990);
        delay(500);
        //
        // 0x476 is PHLCON LEDA=links status, LEDB=receive/transmit
        // enc28j60PhyWrite(PHLCON,0b0000 0100 0111 01 10);
        es.ES_enc28j60PhyWrite(PHLCON,0x476);
        delay(100);
  
        //init the ethernet/ip layer:
        es.ES_init_ip_arp_udp_tcp(mymac,myip,myport);
  
        //################################
        //Setup de los pines de salida
        for(int i = 0; i &lt; 4; i++)
        {
          pinMode(i + 2, OUTPUT);
        }
  
        //Lamp-test
        digitalWrite(primerrelay, HIGH);
        delay(100);
        digitalWrite(primerrelay, LOW);
        comando_rel = -1;
        comando_tmp = -1;
      }
  
      void loop(){
        uint16_t plen, dat_p;
  
        plen = es.ES_enc28j60PacketReceive(BUFFER_SIZE, buf);
  
        /*plen will be unequal to zero if there is a valid packet (without crc error) */
        if(plen!=0){
  
          // arp is broadcast if unknown but a host may also verify the mac address by sending it to a unicast address.
          if(es.ES_eth_type_is_arp_and_my_ip(buf,plen)){
            es.ES_make_arp_answer_from_request(buf);//*******
            return;
          }
  
          // check if ip packets are for us:
          if(es.ES_eth_type_is_ip_and_my_ip(buf,plen)==0){
            return;
          }
  
          if(buf[IP_PROTO_P]==IP_PROTO_ICMP_V &amp;&amp; buf[ICMP_TYPE_P]==ICMP_TYPE_ECHOREQUEST_V){
            es.ES_make_echo_reply_from_request(buf,plen);
            return;
          }
  
          // tcp port www start, compare only the lower byte
          // En la siguiente linea esta la clave para poder implementar puertos mayores a 254
          if (buf[IP_PROTO_P]==IP_PROTO_TCP_V&amp;&amp;buf[TCP_DST_PORT_H_P]==highByte(myport)&amp;&amp;buf[TCP_DST_PORT_L_P]==lowByte(myport)){
            if (buf[TCP_FLAGS_P] &amp; TCP_FLAGS_SYN_V){
              es.ES_make_tcp_synack_from_syn(buf); // make_tcp_synack_from_syn does already send the syn,ack
              return;     
            }
            if (buf[TCP_FLAGS_P] &amp; TCP_FLAGS_ACK_V){
              es.ES_init_len_info(buf); // init some data structures
              dat_p=es.ES_get_tcp_data_pointer();
              if (dat_p==0){ // we can possibly have no data, just ack:
                if (buf[TCP_FLAGS_P] &amp; TCP_FLAGS_FIN_V){
                  es.ES_make_tcp_ack_from_any(buf);
                  //es.ES_make_tcp_ack_from_any(buf, plen, 1);//************
                }
                return;
              }
              //Comparacion de la ip de origen.
              uint8_t match_ip_origen = 1;
  
              for (int i=0; i&lt;4; i++)
              {
                if(buf[IP_SRC_P + i] != ip_origen[i])
                {
                  match_ip_origen = 0;
                  break;
                }
              }/**/
  
              if (match_ip_origen==1)
              {
                if (strncmp("GET ",(char *)&amp;(buf[dat_p]),4)!=0){
                  // head, post and other methods for possible status codes see:
                  // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
                  plen=es.ES_fill_tcp_data_p(buf,0,PSTR("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n&lt;h1&gt;200 OK&lt;/h1&gt;"));
                  goto SENDTCP;
                }
                if (strncmp("/ ",(char *)&amp;(buf[dat_p+4]),2)==0){
                  plen=print_webpage(buf);
                  goto SENDTCP;
                }
  
                //Calculo el estado de los pines
                estado = 0; //Se setea en cero antes de hacer la comprobacion
                estado += digitalRead(primerrelay) * 1 + digitalRead(primerrelay + 1) * 2 + digitalRead(primerrelay + 2) * 4 + digitalRead(primerrelay + 3) * 8;
  
                //#######################################################################
                //Analisis de los parametros y ejecucion de las acciones correspondientes
  
                if (strncmp("/?cmd=",(char *)&amp;(buf[dat_p+4]),6)==0)
                {
                  //cargar los comandos a las variables globales
                  analyse_cmd((char *)&amp;(buf[dat_p+10]));
                  //Analizar el tiempo. Si es mayor que 0 y menor que 10 (1-9)
                  //guardar el estado actual, ejecutar el comando solicitado, y volver al estado anterior.
                  //Si el tiempo es positivo menor que 10, setear el estado temporalmente
                  if(comando_tmp &gt; 0 &amp;&amp; comando_tmp &lt; 10)
                  {
                    //Si el valor es aceptable (0-15), se ejecuta el comando
                    if(comando_rel &gt; -1 &amp;&amp; comando_rel &lt; 16)
                    {
                      //Generar un estado derivado aplicando un OR a nivel de bits con el estado actual
                      uint8_t r = comando_rel | estado;
                      //Ejecutar el nuevo estado obtenido
                      ejecutar_comando(r);
                      //Esperar el tiempo especificado
                      delay(comando_tmp * 1000);
                      //Volver al estado anterior.
                      ejecutar_comando(estado);
                    }
                  }
                  //Si el tiempo es igual a cero, setear el nuevo estado indefinidamente
                  else if(comando_tmp == 0)
                  {
                    //Si el valor es aceptable (0-15), se ejecuta el comando
                    if(comando_rel &gt; -1 &amp;&amp; comando_rel &lt; 16)
                    {
                      //Ejecutar el comando y no revertirlo
                      ejecutar_comando(comando_rel);
                    }
                  }
                }
  
                plen=print_webpage(buf);
      SENDTCP:  
                es.ES_make_tcp_ack_from_any(buf); // send ack for http get//***************
                es.ES_make_tcp_ack_with_data(buf,plen); // send data      
              }
            }
          }
        }
      }
  
      void ejecutar_comando(uint8_t comando)
      {
        //Realiza un and logico con el parametro a nivel de bits. 
        //Enciende o apaga el relay correspondiente.
        //Si el and logico resulta en 0, escribe LOW.
        //Si es diferente a 0, escribe HIGH.
        digitalWrite(primerrelay, (comando &amp; 1));
        digitalWrite(primerrelay + 1, (comando &amp; 2)); 
        digitalWrite(primerrelay + 2, (comando &amp; 4)); 
        digitalWrite(primerrelay + 3, (comando &amp; 8));
      }
  
      void analyse_cmd(char *x)
      {
        //por por default si no hubieran llegado comandos o estan mal 
        comando_rel = -1;
        comando_tmp = -1;
        //verificar que esten todos los caracteres requeridos
        uint8_t i = 0;
        while(x[i]!=' ' &amp;&amp; x[i]!='\0' &amp;&amp; i &lt; 10){
          i++;
        }
        //si tiene 4 son los caracteres necesarios: 2 para los reles y 2 para el timer
        if(i==4){
          String aux = "";
          //verificar por el nro de los reles
          if(is_integer(x[0]) &amp;&amp; is_integer(x[1])){
            aux = String(x[0]) + String(x[1]);
            comando_rel = aux.toInt();
          }
          aux = "";
          //verificar por el nro de segundos del timer
          if(is_integer(x[2]) &amp;&amp; is_integer(x[3])){
            aux = String(x[2]) + String(x[3]);
            comando_tmp = aux.toInt();
          }          
        }
      }
  
      uint8_t is_integer(char c){
        uint8_t r = 0;
        if (c &lt; 0x3a &amp;&amp; c &gt; 0x2f){
          r = 1;
        }  
        return r;
      }
  
      uint16_t print_webpage(uint8_t *buf)
      {
        uint16_t plen, dat_p;
        dat_p=es.ES_get_tcp_data_pointer();
  
        plen=es.ES_fill_tcp_data_p(buf,0,PSTR("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n"));
  
        plen=es.ES_fill_tcp_data_p(buf,plen,PSTR("&lt;center&gt;&lt;p&gt;&lt;h1&gt;Modulo Thor V1.0  &lt;/h1&gt;&lt;/p&gt;&lt;/br&gt;&lt;/hr&gt; "));
  
        String x = String(buf[IP_DST_P]) + "." + String(buf[IP_DST_P+1]) + "." + String(buf[IP_DST_P+2]) + "." + String(buf[IP_DST_P+3]) + " llamado desde ";
        char *s = getCharArray(x);
        plen=es.ES_fill_tcp_data(buf,plen,s);
  
        x = String(buf[IP_SRC_P]) + "." + String(buf[IP_SRC_P+1]) + "." + String(buf[IP_SRC_P+2]) + "." + String(buf[IP_SRC_P+3]) + "&lt;/br&gt;&lt;/center&gt;";
        s = getCharArray(x);
        plen=es.ES_fill_tcp_data(buf,plen,s);
  
        //Al haberse ejecutado un comando el estado resultante debe ser actualizado.
        //Calculo del estado de los pines
        estado = 0; //Se setea en cero antes de hacer la comprobacion
        estado += digitalRead(primerrelay) * 1 + digitalRead(primerrelay + 1) * 2 + digitalRead(primerrelay + 2) * 4 + digitalRead(primerrelay + 3) * 8;
  
        x = "REL: " + String(comando_rel) + "&lt;/br&gt;TMP: " + String(comando_tmp) + "&lt;/br&gt;STA: " + String(estado) + "&lt;/br&gt;&lt;/br&gt;"
  + "&lt;input type=\"hidden\" name=\"status\" value=\"" + (String)estado + "\"&gt;";
        s = getCharArray(x);
        plen=es.ES_fill_tcp_data(buf,plen,s);
  
        return(plen);
      }
  
      char* getCharArray(String s)
      {
        char charBuf[s.length() + 1];
        s.toCharArray(charBuf,s.length() + 1);
        return charBuf;
      }
  
      void reset()
      {
        for (int i = primerrelay; i &lt; primerrelay + 4; i++)
        {
          digitalWrite(i, LOW);
        }
      }
  </code></pre>
  
  <p>thor.php:</p>
  
  <pre><code>    &lt;?php
      //Requiere tener instalado php5-curl
      require 'thorconfig.php';
      require 'common.php';
      $tareas = array();
      //Recorrer las configuraciones y armar la lista de tareas
              foreach($modulos as $modulo) //Recorrer cada módulo
              {
          foreach($modulo["eventos"] as $evento) //Recorrer cada evento de un módulo
          {
              //Si el día y la hora del evento coinciden con el día y la hora actuales
              if(strcmp(date("w"), $evento["dia"]) == 0 &amp;&amp; strcmp(date("H:i"), $evento["hora"]) == 0)
              {
                  //Añadir una tarea con el formato "http://direccion_ip:puerto/?cmd=reltmp"
                  $tareas[] = "http://".$modulo["ip"].":".$modulo["puerto"]."/?cmd=".$evento["rel"].$evento["tmp"];
              }
          }
      }
      $curl = array();
      //Inicializar el handler de tareas
      $curlHandle = curl_multi_init();
      //Recorrer las tareas y añadirlas al handler
      foreach($tareas as $tarea)
          $curl[] = addHandle($curlHandle, $tarea);
      //Ejecutar el handler
      ExecHandle($curlHandle);
      echo "\n";
      //Recuperar la respuesta de cada tarea ejecutada
      for($i = 0; $i &lt; sizeof($tareas); $i++)
      {
          $respuesta = curl_multi_getcontent($curl[$i])."\n";
          if(!strpos($respuesta, "&lt;input type=\"hidden\" name=\"status\""))
          {
              $message = "Ha ocurrido un error al intentar ejecutar el siguiente comando: ".$tareas[$i];
              sendMail($server["from"], $server["from"], $server["to"], $server["to"], "Error en Thor", $message, $server);
          }
          else
          {
              echo $respuesta;
          }
      }
      //Remover cada tarea del handler
      foreach($curl as $handle)
          curl_multi_remove_handle($curlHandle, $handle);
      //Cerrar el handler
      curl_multi_close($curlHandle);
      ?&gt;
  </code></pre>
  
  <p>thorconfig.php</p>
  
  <pre><code>    &lt;?php
          $modulos = [
              "modulo 0" =&gt; [
                  "ip" =&gt; "172.24.51.101", //Teológico
                  "puerto" =&gt; 6174,
                  "eventos" =&gt; [
                      ////////////////////// Lunes //////////////////////
                      "evento 0" =&gt; [
                          "dia" =&gt; 1,
                          "hora" =&gt; "07:30",
                          "rel" =&gt; "01",
                          "tmp" =&gt; "03"
                      ],
                      "evento 1" =&gt; [
                          "dia" =&gt; 1,
                          "hora" =&gt; "08:25",
                          "rel" =&gt; "01",
                          "tmp" =&gt; "03"
                      ]
      .
      .
      .
  
             ]
      ]
  
          $server = [
              "host" =&gt; "172.16.0.40",
              "puerto" =&gt; 25,
              "smtpuser" =&gt; "user",
              "smtppass" =&gt; "pass",
              "to" =&gt; "mail@uap.edu.ar",
              "from" =&gt; "mail@uap.edu.ar"
          ];
      ?&gt;
  </code></pre>
  
  <p>common.php:</p>
  
  <pre><code>    &lt;?php
      //Función que ejecuta el handler
      function ExecHandle(&amp;$curlHandle)
      {
              $flag=null;
              do {
              //fetch pages in parallel
                      curl_multi_exec($curlHandle,$flag);
              } while ($flag &gt; 0);
      }
  
      //Función que añade un recurso al handler
      function addHandle(&amp;$curlHandle,$url)
      {
              $cURL = curl_init();
              curl_setopt($cURL, CURLOPT_URL, $url);
              curl_setopt($cURL, CURLOPT_HEADER, 0);
              curl_setopt($cURL, CURLOPT_RETURNTRANSFER, 1);
              curl_multi_add_handle($curlHandle,$cURL);
              return $cURL;
      }
  
      function sendMail($from, $namefrom, $to, $nameto, $subject, $message, $server)
      {
              $smtpServer = $server["host"];   //ip address of the mail server.  This can also be the local domain name
              $port = $server["puerto"];                    // should be 25 by default, but needs to be whichever port the mail server will be using for smtp
              $timeout = "45";                 // typical timeout. try 45 for slow servers
              $username = $server["smtpuser"]; // the login for your smtp
              $password = $server["smtppass"];           // the password for your smtp
              $localhost = "127.0.0.1";      // Defined for the web server.  Since this is where we are gathering the details for the email
              $newLine = "\r\n";           // aka, carrage return line feed. var just for newlines in MS
              $secure = 0;                  // change to 1 if your server is running under SSL
  
              //connect to the host and port
              $smtpConnect = fsockopen($smtpServer, $port, $errno, $errstr, $timeout);
              $smtpResponse = fgets($smtpConnect, 4096);
              if(empty($smtpConnect)) {
                       $output = "Failed to connect: $smtpResponse";
                       echo $output;
                       return $output;
              }
              else {
                       $logArray['connection'] = "&lt;p&gt;Connected to: $smtpResponse";
                       echo "&lt;p /&gt;connection accepted&lt;br&gt;".$smtpResponse."&lt;p /&gt;Continuing&lt;p /&gt;\n";
              }
  
              //you have to say HELO again after TLS is started
              fputs($smtpConnect, "HELO $localhost". $newLine);
              $smtpResponse = fgets($smtpConnect, 4096);
              $logArray['heloresponse2'] = "$smtpResponse";
              //request for auth login
              fputs($smtpConnect,"AUTH LOGIN" . $newLine);
              $smtpResponse = fgets($smtpConnect, 4096);
              $logArray['authrequest'] = "$smtpResponse";
  
              //send the username
              fputs($smtpConnect, base64_encode($username) . $newLine);
              $smtpResponse = fgets($smtpConnect, 4096);
              $logArray['authusername'] = "$smtpResponse";
  
              //send the password
              fputs($smtpConnect, base64_encode($password) . $newLine);
              $smtpResponse = fgets($smtpConnect, 4096);
              $logArray['authpassword'] = "$smtpResponse";
  
              //email from
              fputs($smtpConnect, "MAIL FROM: &lt;$from&gt;" . $newLine);
              $smtpResponse = fgets($smtpConnect, 4096);
              $logArray['mailfromresponse'] = "$smtpResponse";
  
              //email to
              fputs($smtpConnect, "RCPT TO: &lt;$to&gt;" . $newLine);
              $smtpResponse = fgets($smtpConnect, 4096);
              $logArray['mailtoresponse'] = "$smtpResponse";
  
              //the email
              fputs($smtpConnect, "DATA" . $newLine);
              $smtpResponse = fgets($smtpConnect, 4096);
              $logArray['data1response'] = "$smtpResponse";
  
              //construct headers
              $headers = "MIME-Version: 1.0" . $newLine;
              $headers .= "Content-type: text/html; charset=iso-8859-1" . $newLine;
              $headers .= "To: $nameto &lt;$to&gt;" . $newLine;
              $headers .= "From: $namefrom &lt;$from&gt;" . $newLine;
  
              //observe the . after the newline, it signals the end of message
              fputs($smtpConnect, "To: $to\r\nFrom: $from\r\nSubject: $subject\r\n$headers\r\n\r\n$message\r\n.\r\n");
              $smtpResponse = fgets($smtpConnect, 4096);
              $logArray['data2response'] = "$smtpResponse";
  
              // say goodbye
              fputs($smtpConnect,"QUIT" . $newLine);
              $smtpResponse = fgets($smtpConnect, 4096);
              $logArray['quitresponse'] = "$smtpResponse";
              $logArray['quitcode'] = substr($smtpResponse,0,3);
              fclose($smtpConnect);
              //a return value of 221 in $retVal["quitcode"] is a success
              return($logArray);  
      }
      ?&gt;
  </code></pre>
  
  <p>Any ideas why it executes only once, as it should, when I execute commands within less than 4 minutes and it executes twice otherwise?</p>
  
  <p>EDIT: I discarded the problem being in the PHP code. I installed a lynx text browser in the server and manually executed commands more than 5 minutes apart and got the same result: a duplicated action from Arduino. I leave the PHP code just in case someone is interested in it and may use it. I'll continue to experiment to find a solution.</p>
  
  <p>EDIT 2: I discarded the problem being in the Arduino Hardware. I tested an out-of-the-box new Arduino Uno (same model) with the same code and it still had the same bug.</p>
  
  <p>EDIT 3: Just an idea. Is it possible that the PHP server is expecting an immediate response and as it isn't given by Arduino right away then sends the packet again thus getting a double (late) response from Arduino? Here's another one: Is it possible that the Arduino is passing through the buffer twice and not realizing it? (The second options seems less likely to me).</p>