繁体   English   中英

Arduino客户端+ PHP cURL服务器两次执行命令

[英]Arduino client + PHP cURL server executing command twice

我正在做这个项目,我从一个PHP服务器命令几个Arduino(Arduino内核+ ENC28J60以太网+ x4继电器执行器)模块来激活任何Arduino模块上的继电器。 服务器具有所有事件的列表,并在每个事件合适的时间执行它们。 错的是,每当命令间隔超过4分钟(即> = 5分钟)时,Arduino就会执行两次命令。 也就是说,Arduino连续两次激活了我命令的继电器。

该代码的作用是:1. thor.php被线性执行一次(任务由crontab重复执行)2. thor.php在其数组中搜索当前时间发生的事件3. for每次发生时,它都会生成一个任务,该任务将交付给curl多重处理程序4。所有任务都并行发送到每个arduino模块。 5.当Arduino收到请求时,检查它是否来自已知IP地址并通过允许的端口,分析参数中的命令,并根据请求激活继电器。 6. Arduino然后发送带有隐藏字段的响应页面,该页面将在将来用于控制。

从理论上讲一切正常,但是每隔5分钟或更长时间执行一次命令,Arduino将执行两次命令。

接下来我将整个代码。 这是Arduino :(请用西班牙语评论)

    #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 < 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 && 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&&buf[TCP_DST_PORT_H_P]==highByte(myport)&&buf[TCP_DST_PORT_L_P]==lowByte(myport)){
          if (buf[TCP_FLAGS_P] & 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] & 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] & 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<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 *)&(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<h1>200 OK</h1>"));
                goto SENDTCP;
              }
              if (strncmp("/ ",(char *)&(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 *)&(buf[dat_p+4]),6)==0)
              {
                //cargar los comandos a las variables globales
                analyse_cmd((char *)&(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 > 0 && comando_tmp < 10)
                {
                  //Si el valor es aceptable (0-15), se ejecuta el comando
                  if(comando_rel > -1 && comando_rel < 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 > -1 && comando_rel < 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 & 1));
      digitalWrite(primerrelay + 1, (comando & 2)); 
      digitalWrite(primerrelay + 2, (comando & 4)); 
      digitalWrite(primerrelay + 3, (comando & 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]!=' ' && x[i]!='\0' && i < 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]) && 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]) && 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 < 0x3a && c > 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("<center><p><h1>Modulo Thor V1.0  </h1></p></br></hr> "));

      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]) + "</br></center>";
      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) + "</br>TMP: " + String(comando_tmp) + "</br>STA: " + String(estado) + "</br></br>"
+ "<input type=\"hidden\" name=\"status\" value=\"" + (String)estado + "\">";
      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 < primerrelay + 4; i++)
      {
        digitalWrite(i, LOW);
      }
    }

thor.php:

    <?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 && 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 < sizeof($tareas); $i++)
    {
        $respuesta = curl_multi_getcontent($curl[$i])."\n";
        if(!strpos($respuesta, "<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);
    ?>

thorconfig.php

    <?php
        $modulos = [
            "modulo 0" => [
                "ip" => "172.24.51.101", //Teológico
                "puerto" => 6174,
                "eventos" => [
                    ////////////////////// Lunes //////////////////////
                    "evento 0" => [
                        "dia" => 1,
                        "hora" => "07:30",
                        "rel" => "01",
                        "tmp" => "03"
                    ],
                    "evento 1" => [
                        "dia" => 1,
                        "hora" => "08:25",
                        "rel" => "01",
                        "tmp" => "03"
                    ]
    .
    .
    .

           ]
    ]

        $server = [
            "host" => "172.16.0.40",
            "puerto" => 25,
            "smtpuser" => "user",
            "smtppass" => "pass",
            "to" => "mail@uap.edu.ar",
            "from" => "mail@uap.edu.ar"
        ];
    ?>

common.php:

    <?php
    //Función que ejecuta el handler
    function ExecHandle(&$curlHandle)
    {
            $flag=null;
            do {
            //fetch pages in parallel
                    curl_multi_exec($curlHandle,$flag);
            } while ($flag > 0);
    }

    //Función que añade un recurso al handler
    function addHandle(&$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'] = "<p>Connected to: $smtpResponse";
                     echo "<p />connection accepted<br>".$smtpResponse."<p />Continuing<p />\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: <$from>" . $newLine);
            $smtpResponse = fgets($smtpConnect, 4096);
            $logArray['mailfromresponse'] = "$smtpResponse";

            //email to
            fputs($smtpConnect, "RCPT TO: <$to>" . $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 <$to>" . $newLine;
            $headers .= "From: $namefrom <$from>" . $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);  
    }
    ?>

有什么想法为什么当我在不到4分钟的时间内执行命令时,它只执行一次,而应该执行两次?

编辑:我放弃了在PHP代码中的问题。 我在服务器中安装了天猫座文本浏览器,并且手动执行了间隔超过5分钟的命令,并得到了相同的结果:Arduino的重复动作。 我留下PHP代码是为了以防万一有人对它感兴趣并且可以使用它。 我将继续尝试寻找解决方案。

编辑2:我放弃了在Arduino硬件中的问题。 我用相同的代码测试了一个开箱即用的新Arduino Uno(相同型号),它仍然具有相同的错误。

编辑3:只是一个想法。 PHP服务器是否有可能期望立即响应,而Arduino并没有立即给出响应,然后再次发送数据包,从而得到Arduino的双重(后期)响应? 这是另一个:Arduino是否有可能两次通过缓冲区而没有意识到呢? (第二种选择对我来说似乎不太可能)。

使用服务器上的网络嗅探器(即Wireshark)来查看实际发送的内容。 这样,您就可以轻松测试第3个想法。wireshark还可以选择重播流量,从而使测试更加容易。

我通过添加补丁(不是永久解决方案)“解决了”问题。 我仍然需要找到问题的根源。

#include "etherShield.h"行之后添加了此内容

#include <TimedAction.h>

//Para control de ejecuciones
int16_t ultimo_comando_ejecutado[5];
int16_t ultimo_tiempo_ejecutado[5];
//Number of seconds since last command execution or execution register.
int8_t segundos;
TimedAction ta = TimedAction(1000,revisar);

这在setup()

resetear_registro();
segundos = 0;

loop()的第一行

ta.check();

这在命令执行

if(ejecutado(comando_rel, comando_tmp) == 0)
{
  ejecutar_comando(r);
  registrar_ejecucion(comando_rel, comando_tmp);
  //Esperar el tiempo especificado
  delay(comando_tmp * 1000);
  //Volver al estado anterior.
  ejecutar_comando(estado);
}

和这个

if(ejecutado(comando_rel, comando_tmp) == 0)
{
  ejecutar_comando(comando_rel);
  registrar_ejecucion(comando_rel, comando_tmp);
}

最后,这些功能最后

void registrar_ejecucion(int16_t cmd, int16_t tmp)
{
  for(int i = 0; i < 4; i++)
  {
    ultimo_comando_ejecutado[i] = ultimo_comando_ejecutado[i+1];
    ultimo_tiempo_ejecutado[i] = ultimo_tiempo_ejecutado[i+1];
  }
  ultimo_comando_ejecutado[4] = cmd;
  ultimo_tiempo_ejecutado[4] = tmp;
  segundos = 0;
}

uint8_t ejecutado(int16_t cmd, int16_t tmp)
{
  uint8_t ejec = 0;
  for(int i = 0; i < 5; i++)
  {
    if(ultimo_comando_ejecutado[i] == cmd && ultimo_tiempo_ejecutado[i] == tmp)
    {
      ejec = 1;
      break;
    }
  }
  return ejec;
}

void resetear_registro()
{
  for(int a = 0; a < 5; a++)
  {
    ultimo_comando_ejecutado[a] = -1;
    ultimo_tiempo_ejecutado[a] = -1;
  }
}

void revisar()
{
  segundos++;
  if(segundos > 59)
  {
    resetear_registro();
    segundos = 0;
  }
}

基本上,它的工作是检查在最后一分钟内执行的最后5条命令(继电器和时间),如果有匹配项,则忽略该命令。 在低规模上,它可以解决问题。 但是我意识到这只是一个补丁,可能会出现问题。 现在,我将按原样(加上补丁)实现代码。 但是,如果有人找到更好,更持久的解决方案,我欢迎您提出建议。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM