Comunicación entre Arduino y Servidor web utilizando PHP




Voy a dedicar este post a explicar, un poco más en detalle, la comunicación entre Arduino y el servidor web a través del puerto serie (en este caso, USB), utilizando una clase de PHP creada por Rémy Sanchez.

El principio es el mismo que utiliza la aplicación de Arduino para comunicarse con la placa a través del "Serial Monitor". La cuestión aquí es, como enviar y recibir esos datos desde nuestro servidor web.

Yo he utilizado una clase de PHP, ya preparada, para comunicarme con el puerto serie del ordenador. Esta clase funciona perfectamente sobre un servidor en Linux. Según su autor, también es compatible con Windows, pero solo para enviar mensajes.

No hace falta más que este pequeño fragmento de código, para empezar a jugar:
<?php 

   // incluímos la clase necesaria para la comunicación
   require("php_serial.class.php");

   $serial = new phpSerial;
   // indicamos que puerto serie queremos usar
   $serial->deviceSet("/dev/ttyACM0");
   // ahora la velocidad de transmisión de Arduino
   $serial->confBaudRate(115200);
   $serial->deviceOpen();

   // aquí el String que queremos enviar
   $serial->sendMessage("light");
   sleep (1);
   
   $serial->deviceClose();

?>

Yo de momento solo lo he utilizado para enviar. Pero en caso de querer recibir habría que utilizar la función readPort(). Como parámetro podemos enviarle el número de caracteres que debe leer, nos devuelve un String.
<?php

   // guardamos en $read la cadena de 
   // caracteres que hemos recibido de Arduino
   $read = $serial->readPort();

?>

A continuación os dejo la clase necesaria para poder usar estas funciones. Unicamente tendréis que guardarla en un archivo a parte, respetando el nombre que os indico como título del código, para que os funcione con el ejemplo que he puesto arriba.



php_serial.class.php

 <?php  
 define ("SERIAL_DEVICE_NOTSET", 0);  
 define ("SERIAL_DEVICE_SET", 1);  
 define ("SERIAL_DEVICE_OPENED", 2);  
   
 /**  
  * Serial port control class  
  *  
  * THIS PROGRAM COMES WITH ABSOLUTELY NO WARANTIES !  
  * USE IT AT YOUR OWN RISKS !  
  *  
  * Changes added by Rizwan Kassim <rizwank@uwink.com> for OSX functionality  
  * default serial device for osx devices is /dev/tty.serial for machines with a built in serial device  
  *  
  * @author RÈmy Sanchez <thenux@gmail.com>  
  * @thanks AurÈlien Derouineau for finding how to open serial ports with windows  
  * @thanks Alec Avedisyan for help and testing with reading  
  * @copyright under GPL 2 licence  
  *  
  * do not change this code unless you know what you are getting into. Leaving it as you have received it should work fine!  
  */  
 class phpSerial  
 {  
      var $_device = null;  
      var $_windevice = null;  
      var $_dHandle = null;  
      var $_dState = SERIAL_DEVICE_NOTSET;  
      var $_buffer = "";  
      var $_os = "";  
   
      /**  
       * This var says if buffer should be flushed by sendMessage (true) or manualy (false)  
       *  
       * @var bool  
       */  
      var $autoflush = true;  
   
      /**  
       * Constructor. Perform some checks about the OS and setserial  
       *  
       * @return phpSerial  
       */  
      function phpSerial ()  
      {  
           setlocale(LC_ALL, "en_US");  
   
           $sysname = php_uname();  
   
           if (substr($sysname, 0, 5) === "Linux")  
           {  
                $this->_os = "linux";  
   
                if($this->_exec("stty --version") === 0)  
                {  
                     register_shutdown_function(array($this, "deviceClose"));  
                }  
                else  
                {  
                     trigger_error("No stty availible, unable to run.", E_USER_ERROR);  
                }  
           }  
           elseif (substr($sysname, 0, 6) === "Darwin")  
           {  
                $this->_os = "osx";  
       // We know stty is available in Darwin.   
       // stty returns 1 when run from php, because "stty: stdin isn't a  
       // terminal"  
       // skip this check  
 //               if($this->_exec("stty") === 0)  
 //               {  
                     register_shutdown_function(array($this, "deviceClose"));  
 //               }  
 //               else  
 //               {  
 //                    trigger_error("No stty availible, unable to run.", E_USER_ERROR);  
 //               }  
           }  
           elseif(substr($sysname, 0, 7) === "Windows")  
           {  
                $this->_os = "windows";  
                register_shutdown_function(array($this, "deviceClose"));  
           }  
           else  
           {  
                trigger_error("Host OS is neither osx, linux nor windows, unable tu run.", E_USER_ERROR);  
                exit();  
           }  
      }  
   
      //  
      // OPEN/CLOSE DEVICE SECTION -- {START}  
      //  
   
      /**  
       * Device set function : used to set the device name/address.  
       * -> linux : use the device address, like /dev/ttyS0  
       * -> osx : use the device address, like /dev/tty.serial  
       * -> windows : use the COMxx device name, like COM1 (can also be used  
       *   with linux)  
       *  
       * @param string $device the name of the device to be used  
       * @return bool  
       */  
      function deviceSet ($device)  
      {  
           if ($this->_dState !== SERIAL_DEVICE_OPENED)  
           {  
                if ($this->_os === "linux")  
                {  
                     if (preg_match("@^COM(\d+):?$@i", $device, $matches))  
                     {  
                          $device = "/dev/ttyS" . ($matches[1] - 1);  
                     }  
   
                     if ($this->_exec("stty -F " . $device) === 0)  
                     {  
                          $this->_device = $device;  
                          $this->_dState = SERIAL_DEVICE_SET;  
                          return true;  
                     }  
                }  
                elseif ($this->_os === "osx")  
                {  
                     if ($this->_exec("stty -f " . $device) === 0)  
                     {  
                          $this->_device = $device;  
                          $this->_dState = SERIAL_DEVICE_SET;  
                          return true;  
                     }  
                }  
                elseif ($this->_os === "windows")  
                {  
                     if (preg_match("@^COM(\d+):?$@i", $device, $matches) and $this->_exec(exec("mode " . $device)) === 0)  
                     {  
                          $this->_windevice = "COM" . $matches[1];  
                          $this->_device = "\\.\com" . $matches[1];  
                          $this->_dState = SERIAL_DEVICE_SET;  
                          return true;  
                     }  
                }  
   
                trigger_error("Specified serial port is not valid", E_USER_WARNING);  
                return false;  
           }  
           else  
           {  
                trigger_error("You must close your device before to set an other one", E_USER_WARNING);  
                return false;  
           }  
      }  
   
      /**  
       * Opens the device for reading and/or writing.  
       *  
       * @param string $mode Opening mode : same parameter as fopen()  
       * @return bool  
       */  
      function deviceOpen ($mode = "r+b")  
      {  
           if ($this->_dState === SERIAL_DEVICE_OPENED)  
           {  
                trigger_error("The device is already opened", E_USER_NOTICE);  
                return true;  
           }  
   
           if ($this->_dState === SERIAL_DEVICE_NOTSET)  
           {  
                trigger_error("The device must be set before to be open", E_USER_WARNING);  
                return false;  
           }  
   
           if (!preg_match("@^[raw]\+?b?$@", $mode))  
           {  
                trigger_error("Invalid opening mode : ".$mode.". Use fopen() modes.", E_USER_WARNING);  
                return false;  
           }  
   
           $this->_dHandle = @fopen($this->_device, $mode);  
   
           if ($this->_dHandle !== false)  
           {  
                stream_set_blocking($this->_dHandle, 0);  
                $this->_dState = SERIAL_DEVICE_OPENED;  
                return true;  
           }  
   
           $this->_dHandle = null;  
           trigger_error("Unable to open the device", E_USER_WARNING);  
           return false;  
      }  
   
      /**  
       * Closes the device  
       *  
       * @return bool  
       */  
      function deviceClose ()  
      {  
           if ($this->_dState !== SERIAL_DEVICE_OPENED)  
           {  
                return true;  
           }  
   
           if (fclose($this->_dHandle))  
           {  
                $this->_dHandle = null;  
                $this->_dState = SERIAL_DEVICE_SET;  
                return true;  
           }  
   
           trigger_error("Unable to close the device", E_USER_ERROR);  
           return false;  
      }  
   
      //  
      // OPEN/CLOSE DEVICE SECTION -- {STOP}  
      //  
   
      //  
      // CONFIGURE SECTION -- {START}  
      //  
   
      /**  
       * Configure the Baud Rate  
       * Possible rates : 110, 150, 300, 600, 1200, 2400, 4800, 9600, 38400,  
       * 57600 and 115200.  
       *  
       * @param int $rate the rate to set the port in  
       * @return bool  
       */  
      function confBaudRate ($rate)  
      {  
           if ($this->_dState !== SERIAL_DEVICE_SET)  
           {  
                trigger_error("Unable to set the baud rate : the device is either not set or opened", E_USER_WARNING);  
                return false;  
           }  
   
           $validBauds = array (  
                110  => 11,  
                150  => 15,  
                300  => 30,  
                600  => 60,  
                1200  => 12,  
                2400  => 24,  
                4800  => 48,  
                9600  => 96,  
                19200 => 19,  
                38400 => 38400,  
                57600 => 57600,  
                115200 => 115200  
           );  
   
           if (isset($validBauds[$rate]))  
           {  
                if ($this->_os === "linux")  
                {  
         $ret = $this->_exec("stty -F " . $this->_device . " " . (int) $rate, $out);  
       }  
       if ($this->_os === "darwin")  
       {  
         $ret = $this->_exec("stty -f " . $this->_device . " " . (int) $rate, $out);  
       }  
       elseif ($this->_os === "windows")  
       {  
         $ret = $this->_exec("mode " . $this->_windevice . " BAUD=" . $validBauds[$rate], $out);  
       }  
       else return false;  
   
                if ($ret !== 0)  
                {  
                     trigger_error ("Unable to set baud rate: " . $out[1], E_USER_WARNING);  
                     return false;  
                }  
           }  
      }  
   
      /**  
       * Configure parity.  
       * Modes : odd, even, none  
       *  
       * @param string $parity one of the modes  
       * @return bool  
       */  
      function confParity ($parity)  
      {  
           if ($this->_dState !== SERIAL_DEVICE_SET)  
           {  
                trigger_error("Unable to set parity : the device is either not set or opened", E_USER_WARNING);  
                return false;  
           }  
   
           $args = array(  
                "none" => "-parenb",  
                "odd" => "parenb parodd",  
                "even" => "parenb -parodd",  
           );  
   
           if (!isset($args[$parity]))  
           {  
                trigger_error("Parity mode not supported", E_USER_WARNING);  
                return false;  
           }  
   
           if ($this->_os === "linux")  
           {  
                $ret = $this->_exec("stty -F " . $this->_device . " " . $args[$parity], $out);  
           }  
           else  
           {  
                $ret = $this->_exec("mode " . $this->_windevice . " PARITY=" . $parity{0}, $out);  
           }  
   
           if ($ret === 0)  
           {  
                return true;  
           }  
   
           trigger_error("Unable to set parity : " . $out[1], E_USER_WARNING);  
           return false;  
      }  
   
      /**  
       * Sets the length of a character.  
       *  
       * @param int $int length of a character (5 <= length <= 8)  
       * @return bool  
       */  
      function confCharacterLength ($int)  
      {  
           if ($this->_dState !== SERIAL_DEVICE_SET)  
           {  
                trigger_error("Unable to set length of a character : the device is either not set or opened", E_USER_WARNING);  
                return false;  
           }  
   
           $int = (int) $int;  
           if ($int < 5) $int = 5;  
           elseif ($int > 8) $int = 8;  
   
           if ($this->_os === "linux")  
           {  
                $ret = $this->_exec("stty -F " . $this->_device . " cs" . $int, $out);  
           }  
           else  
           {  
                $ret = $this->_exec("mode " . $this->_windevice . " DATA=" . $int, $out);  
           }  
   
           if ($ret === 0)  
           {  
                return true;  
           }  
   
           trigger_error("Unable to set character length : " .$out[1], E_USER_WARNING);  
           return false;  
      }  
   
      /**  
       * Sets the length of stop bits.  
       *  
       * @param float $length the length of a stop bit. It must be either 1,  
       * 1.5 or 2. 1.5 is not supported under linux and on some computers.  
       * @return bool  
       */  
      function confStopBits ($length)  
      {  
           if ($this->_dState !== SERIAL_DEVICE_SET)  
           {  
                trigger_error("Unable to set the length of a stop bit : the device is either not set or opened", E_USER_WARNING);  
                return false;  
           }  
   
           if ($length != 1 and $length != 2 and $length != 1.5 and !($length == 1.5 and $this->_os === "linux"))  
           {  
                trigger_error("Specified stop bit length is invalid", E_USER_WARNING);  
                return false;  
           }  
   
           if ($this->_os === "linux")  
           {  
                $ret = $this->_exec("stty -F " . $this->_device . " " . (($length == 1) ? "-" : "") . "cstopb", $out);  
           }  
           else  
           {  
                $ret = $this->_exec("mode " . $this->_windevice . " STOP=" . $length, $out);  
           }  
   
           if ($ret === 0)  
           {  
                return true;  
           }  
   
           trigger_error("Unable to set stop bit length : " . $out[1], E_USER_WARNING);  
           return false;  
      }  
   
      /**  
       * Configures the flow control  
       *  
       * @param string $mode Set the flow control mode. Availible modes :  
       *      -> "none" : no flow control  
       *      -> "rts/cts" : use RTS/CTS handshaking  
       *      -> "xon/xoff" : use XON/XOFF protocol  
       * @return bool  
       */  
      function confFlowControl ($mode)  
      {  
           if ($this->_dState !== SERIAL_DEVICE_SET)  
           {  
                trigger_error("Unable to set flow control mode : the device is either not set or opened", E_USER_WARNING);  
                return false;  
           }  
   
           $linuxModes = array(  
                "none"   => "clocal -crtscts -ixon -ixoff raw",  
                "rts/cts" => "-clocal crtscts -ixon -ixoff",  
                "xon/xoff" => "-clocal -crtscts ixon ixoff"  
           );  
           $windowsModes = array(  
                "none"   => "xon=off octs=off rts=on",  
                "rts/cts" => "xon=off octs=on rts=hs",  
                "xon/xoff" => "xon=on octs=off rts=on",  
           );  
   
           if ($mode !== "none" and $mode !== "rts/cts" and $mode !== "xon/xoff") {  
                trigger_error("Invalid flow control mode specified", E_USER_ERROR);  
                return false;  
           }  
   
           if ($this->_os === "linux")  
                $ret = $this->_exec("stty -F " . $this->_device . " " . $linuxModes[$mode], $out);  
           else  
                $ret = $this->_exec("mode " . $this->_windevice . " " . $windowsModes[$mode], $out);  
   
           if ($ret === 0) return true;  
           else {  
                trigger_error("Unable to set flow control : " . $out[1], E_USER_ERROR);  
                return false;  
           }  
      }  
   
      /**  
       * Sets a setserial parameter (cf man setserial)  
       * NO MORE USEFUL !  
       *      -> No longer supported  
       *      -> Only use it if you need it  
       *  
       * @param string $param parameter name  
       * @param string $arg parameter value  
       * @return bool  
       */  
      function setSetserialFlag ($param, $arg = "")  
      {  
           if (!$this->_ckOpened()) return false;  
   
           $return = exec ("setserial " . $this->_device . " " . $param . " " . $arg . " 2>&1");  
   
           if ($return{0} === "I")  
           {  
                trigger_error("setserial: Invalid flag", E_USER_WARNING);  
                return false;  
           }  
           elseif ($return{0} === "/")  
           {  
                trigger_error("setserial: Error with device file", E_USER_WARNING);  
                return false;  
           }  
           else  
           {  
                return true;  
           }  
      }  
   
      //  
      // CONFIGURE SECTION -- {STOP}  
      //  
   
      //  
      // I/O SECTION -- {START}  
      //  
   
      /**  
       * Sends a string to the device  
       *  
       * @param string $str string to be sent to the device  
       * @param float $waitForReply time to wait for the reply (in seconds)  
       */  
      function sendMessage ($str, $waitForReply = 0.1)  
      {  
           $this->_buffer .= $str;  
   
           if ($this->autoflush === true) $this->serialflush();  
   
           usleep((int) ($waitForReply * 1000000));  
      }  
   
      /**  
       * Reads the port until no new datas are availible, then return the content.  
       *  
       * @pararm int $count number of characters to be read (will stop before  
       *      if less characters are in the buffer)  
       * @return string  
       */  
      function readPort ($count = 0)  
      {  
           if ($this->_dState !== SERIAL_DEVICE_OPENED)  
           {  
                trigger_error("Device must be opened to read it", E_USER_WARNING);  
                return false;  
           }  
   
           if ($this->_os === "linux" || $this->_os === "osx")  
                {  
                // Behavior in OSX isn't to wait for new data to recover, but just grabs what's there!  
                // Doesn't always work perfectly for me in OSX  
                $content = ""; $i = 0;  
   
                if ($count !== 0)  
                {  
                     do {  
                          if ($i > $count) $content .= fread($this->_dHandle, ($count - $i));  
                          else $content .= fread($this->_dHandle, 128);  
                     } while (($i += 128) === strlen($content));  
                }  
                else  
                {  
                     do {  
                          $content .= fread($this->_dHandle, 128);  
                     } while (($i += 128) === strlen($content));  
                }  
   
                return $content;  
           }  
           elseif ($this->_os === "windows")  
           {  
                /* Do nothing : not implented yet */  
           }  
   
           trigger_error("Reading serial port is not implemented for Windows", E_USER_WARNING);  
           return false;  
      }  
   
      /**  
       * Flushes the output buffer  
       * Renamed from flush for osx compat. issues  
       *  
       * @return bool  
       */  
      function serialflush ()  
      {  
           if (!$this->_ckOpened()) return false;  
   
           if (fwrite($this->_dHandle, $this->_buffer) !== false)  
           {  
                $this->_buffer = "";  
                return true;  
           }  
           else  
           {  
                $this->_buffer = "";  
                trigger_error("Error while sending message", E_USER_WARNING);  
                return false;  
           }  
      }  
   
      //  
      // I/O SECTION -- {STOP}  
      //  
   
      //  
      // INTERNAL TOOLKIT -- {START}  
      //  
   
      function _ckOpened()  
      {  
           if ($this->_dState !== SERIAL_DEVICE_OPENED)  
           {  
                trigger_error("Device must be opened", E_USER_WARNING);  
                return false;  
           }  
   
           return true;  
      }  
   
      function _ckClosed()  
      {  
           if ($this->_dState !== SERIAL_DEVICE_CLOSED)  
           {  
                trigger_error("Device must be closed", E_USER_WARNING);  
                return false;  
           }  
   
           return true;  
      }  
   
      function _exec($cmd, &$out = null)  
      {  
           $desc = array(  
                1 => array("pipe", "w"),  
                2 => array("pipe", "w")  
           );  
   
           $proc = proc_open($cmd, $desc, $pipes);  
   
           $ret = stream_get_contents($pipes[1]);  
           $err = stream_get_contents($pipes[2]);  
   
           fclose($pipes[1]);  
           fclose($pipes[2]);  
   
           $retVal = proc_close($proc);  
   
           if (func_num_args() == 2) $out = array($ret, $err);  
           return $retVal;  
      }  
   
      //  
      // INTERNAL TOOLKIT -- {STOP}  
      //  
 }
   
Espero que os sirva de ayuda en vuestros proyectos. Y si os surge alguna duda, dejad un comentario.

Un saludo!
Continue reading

Cámaras

Para este proyecto me gustaría utilizar un total de tres cámaras. Tomando como referencia los videojuegos diríamos: una en tercera persona, una en segunda y la última en primera persona.
- La cámara en tercera persona sería fija, estará instalada en algún punto de la habitación en la que se vaya a desplazar el robot, mostrando todo el ángulo posible para poder ver en que situación se encuentra el vehículo.
- La cámara en segunda persona es inalámbrica, iría colocada sobre el vehículo, de forma que pudieramos ver el cuerpo del coche y parte del terreno que tenemos delante.
- La cámara en primera persona también es inalámbrica. Iría montada en el morro del coche, para mostrarnos lo que tenemos justo delante de nosotros. Esta cámara está pensada para los movimientos que requieren más precisión, como coger cosas, arrastrarlas, disparar...

En este post quiero centrarme en las cámaras que irán montadas en el vehículo, ya que la cámara fija será una simple cámara USB, sin mayor complicación.


Objetivo: Instalar cámaras inalámbricas en el vehículo que sean relativamente económicas y no muy pesadas.


PRIMERA OPCIÓN:



Podría utilizar el mismo sistema que se utiliza en aeromodelismo para vuelo FPV (First Person View).
Esto significa utilizar una mini cámara PAL con una emisora para la señal de video. Que en este caso podemos utilizar la versión que ya lleva la emisora incorporada, ya que no tenemos tanto problema de espacio. Estas camaritas pesan muy poco y el consumo es muy bajo. Podríamos estar hablando de unos 150g para una cámara con visión nocturna y una calidad de video relatavimente buena. Y un consumo de 120mAh con los IR apagados.
Necesitaremos un receptos de video, que no es gran problema, ya que los hay por menos de 30€ que dan bastante buen resultado.
El problema lleva a la hora de llevar la señal de video al PC. Necesitaremos una capturadora con varias entradas de video, que puede resultar un problema si queremos tener el servidor bajo linux. Aunque para windows hay saluciones muy económicas. Yo de momento sigo sin encontrar una tarjeta que no se dispare de precio y que me puedan asegurar que funciona sobre linux.
Todo esto sin tener en cuenta que la señal de video se transmite a 2,4GHz y puede interferir con la WLAN

Consumo total: 150mAh.
Coste total: (Cámara 35€) + (Receptor 27€) + (Capturadora 50€) = 112€
Cámara extra: (Cámara 35€) + (Receptor 27€) = 62€
Pros:
- Poco peso.
- Muy bajo consumo.
Contras:
- Incompatibilidad con Linux.
- Interferencias con la Wlan.



SEGUNDA OPCIÓN:

Esta sería utilizando una cámara IP inalámbrica. Como ejemplo he tomado la FOSCAM FI8908W. Esta cámara tiene una calidad de imagen bastante buena. También dispone de visión nocturna y tiene un alcance de hasta 15 metros.
Utilizando una cámara IP se acabarían los problemas de incompatibilidad, y tampoco necesitamos accesorios, si nuestro router dispone de wifi.
Los inconvenientes de esta cámara son el peso y el consumo. De serie pesa unos 400g, que espero poder rebajar quitándole la base y toda la carcasa que no sea necesaria. En caso de no que motorizarla, también me puedo ahorrar los servos, si es que son muy pesado o consumen mucho (está por ver). El consumo está cerca de 500mAh con los IR apagados, que también espero poder rebajar desmontando las partes del circuito que no sean necesarias, pero esto ya lo veo menos probable.

Consumo total: 500mAh.
Coste total: (Cámara 60€) + (Bateria extra 30€) = 90€
Cámara extra: (Cámara 60€) = 62€
Pros:
- Fácil configuración.
- Buena calidad de video.
Contras:
- Mucho peso.
- Consumo elevado.



He encontrado algunas otras soluciones, que he descartado por ser muy parecidas a estas dos, o por necesitar demasiado tiempo de desarrollo.

De todos modos, si conoceis alguna otra opción que penseis que puede ser mejor que cualquiera de estas dos, os agradecería que la compartierais, igual que cualquier consejo que podais darme sobre el tema.

Gracias!
Continue reading

Esquema de conexiones

Sigo trabajando en el interfaz gráfico para enviar órdenes al vehículo desde la web. Mientras tanto, he hecho un boceto de conexión entre los dispositivos entre sí.

Así es como quedaría:



Batería: He decidido usar una lipo de 2 celdas de unos 5000mAh. Que de momento pienso que le permitirá funcionar durante un buen rato, y con 7,2V que da, puedo alimentarlo todo (Arduino, motores, webcam...).

Arduino: Una Arduino Mega 2560 va a ser el centro del proyecto.

Conexión inalámbrica:
- La primera opción era comprarme todo el kit para poder utilizar XBee. Me parecía una idea estupenda, pero el precio del kit completo sale algo caro y realmente no necesito tanto alcance para la comunicación, de momento, con unos 10-20 metros es suficiente. Este kit constaría de: 1x Xbee Shield For Arduino, 1x Xbee USB adapter y 2x XBee 2mW Wire Antenna. Estamos hablando de unos 60€ en total.

- La segunda opción, que me parece más razonable es comunicarse a través de Bluetooth. Es una muy sencilla, me permite utilizar la misma velocidad de transferencia y es mucho más ecónomica. Hay mucha variedad, con más o menos alcance, con modo host o solo esclavo, etc. Sin ir más lejos, este modelo con modo Host y casi 30m de alcance cuesta cerca de 24€.

Finalmente he decidido quedarme con el Bluetooth, aunque con una versión más económica. Con unos 20 metros de alcance y solo con modo esclavo me cuesta cerca de 10€.

Controladora para motores:
Estoy entre dos opciones:


- La primera es el Adafruit Motor Shield, que me permite controlar hasta cuatro motores DC, dos motores paso a paso y dos servos, pero solo me permite usar motores de hasta 0,6A. El precio es de aproximadamente 20€.


- La segunda opción es comprar dos controladoras 2A Dual Motor Controller de DFRobot, que me permiten controlar dos motores de hasta 2A cada una. Esto ya me gusta más. Y el precio de las dos placas suma unos 24€.

Así que he pensado quedarme con la segunda opción, ya que la diferencia de precio es poca y me va a permitir usar motores mucho más potentes en el futuro.

Motores: El chasis viene equipado de casa con cuatro motores Micro DC Geared Motor de DFRobot. Vienen con reductora 1:120, funcionan entre 3V y 6V, y alcazan las 180rpm, que es más que suficiente para empezar.

Cámara: He pensado utilizar una webcam inalámbrica con receptor USB. He encontrado algunas relativamente económicas, pero de momento ninguna compatible con Linux. Me estoy planteando el montarla sobre un servo para poder orientar la cámara sin tener que girar el coche.

Linterna: Quiero equiparlo con una linternita LED, pero todavía no se si de LEDs de alta luminiscencia o de infrarrojos. El tema es que sea posible jugar con el cochecito incluso cuando sea de noche. Eso si, apagad la luz cuando hayais terminado :P

Un saludo!
Continue reading

Desactivando el "Auto-Reset Enable Trace"

Esta mañana he descubierto que era lo que me estaba dando tantos problemas al comunicarme con la placa.

Resulta que las Arduino tienen un jumper llamado "RESET-EN" que viene puenteado por defecto. Con esto consiguen que podamos programas la placa desde nuestra aplicación e inmediatamente después se ejecute el programa que acabamos de flashear. Esto es una comodidad, pero inhabilita las comunicaciones por el puerto serie, ya que resetea nuestro Arduino después de cada comunicación.

Bueno, pues lo único que tenemos que hacer es cortar el jumper, para que no se vuelva a resetear automáticamente.

En el caso de la Arduino Mega 2560 encontramos el jumper aquí:


Hay que pasar con un bisturí o cutter entre los dos puntos de soldadura.
Con mucho cuidado y sin salirse del rectangulito blanco:



Una vez hecho esto, podemos comunicarnos con la placa a través del USB sin ningún problema.

A disfrutar!
Continue reading

Control remoto del puerto serie a través de la web

Por fin me ha llegado el servidor!
Estaba en perfecto estado, así que le he puesto un disco duro y ya está funcionando.

He seguido peleándome con PHP para conseguir una comunicación con la placa que fuera relativamente limpia (sin fallos). De momento a la conclusión que he llegado es que funciona mucho mejor si no meto AJAX de por medio.

Esta sería la comunicación que estoy utilizando ahora mismo:


Y esta como pretendo que sea en el futuro:

De momento a mí no me ha fallado, pero si agradecería que comentarais vuestra experiencia.

Estoy actualizando el servidor. Pronto volverá a estar online!

Gracias! :D
Continue reading

Preparando el servidor



El pc que compré sigue sin llegar. Pero quería ir haciendo pruebas para poder ir quitandome problemas de encima. Así que he dedicado el fin de semana a instalar y probar todos los programas que voy a necesitar para el servidor final.

Allá vamos:
He instalado Ubuntu 10.10.
Nada mas arracanar el sistema, instalé "tasksel", una forma muy sencilla para instalar paquetes de software. Con él, pude instalar y configurar LAMP en muy pocos pasos.
Como servidor FTP he instalado "vsftpd".

Una vez terminado de configurar todo e iniciado Apache, empecé a abrirle todas las puertas a mi servidor, para hacerlo accesible desde fuera.

Para ello solo tuve que abrir los puertos en mi red, dar permisos de acceso a programas y archivos, y buscar un servicio que redirija mi ip-dinámica a una dirección ip fija. Yo siempre utilizo "dondominio.com" como hosting web (altamente recomendable), y por suerte tenía también este servicio para DNSs dinámicas. Así que estaba chupado! Incluyes un script en tu servidor y listo. Las DNS de tu dominio web se actualizan periódicamente para apuntar al servidor que tienes en casa.

Hasta aquí me lo conocía todo más o menos. Lo había hecho un par de veces en windows, pero nunca con linux, y diria que con windows siempre se me ha hecho más largo.

El servidor web ya está listo!


Ahora viene lo que de verdad me ha dado dolores de cabeza, y todavía no he encontrado una solución: Streaming de video y utilizar el puerto serie desde el servidor web.

Streaming de video en vivo:
Para este tema, en teoría, lo más sencillo es utilizar VLC, pero después de dar muchas vueltas no conseguí hacerlo funcionar como yo quería.
Una solución rápida fue utilizar "motion", un programita para capturar video, que también es muy sencillo de instalar y poner en marcha. Con la ventaja de que este programa solo emite video si hay alguien observando el puerto de la cámara o si hay movimiento delante de la cámara. El problema sigue siendo convertir este streaming a un formato que sea visible por todos los exploradores, para lo que quiero utilizar "mjpeg-proxygrab" (de momento no funciona con IE).
Sigo peleandome para mejorarlo.


Utilizar el puerto serie desde el servidor:
Me ha sido muy fácil comunicarme con el puerto serie utilizando PHP. El tema es que no soy yo el que tiene que ejecutar el código, sino los usuarios de la página web.
Así que necesito ejecutar código PHP en el servidor, sin modificar la vista del usuario. AJAX! sería la respuesta... pero no, estoy teniendo muchos problemas para que funcione correctamente. Estoy combinando PHP y AJAX de todas las formas posible para hacerlo funcionar sin errores, y no siempre se ejecuta el código de PHP cuando el usuario lo solicita y muchas veces no hace lo que debería hacer.
Mi conclusión es que estoy llamando mal a PHP desde AJAX. Tengo que seguir viendo este tema...

De momento tenemos video y control desde fuera, ahora solamente hay que depurarlo un poquito!
Continue reading

Comunicación: Servidor - Arduino

Mientras llega el servidor para poder instalarle LAMP, voy dándole vueltas a como organizar la comunicación entre el servidor y la placa.

De momento he hecho un esquema de comunicación muy sencillo, de como funcionaría la gestión de operaciones que enviará el usuario al GE:




Puesto que de momento no creo que disponga de suficiente velocidad como para hacer un buen streaming con varias cámaras a la vez, y también porque me parece que tiene su gracia, quiero programar el interfaz de control del GE de tal forma que el usuario podrá crear una lista de comandos, que el robot tendrá que ejecutar de forma secuencial.

Para ello el usuario seleccionará las ordenes teniendo en cuenta todos los datos que tiene a su disposición, sobre la posición del robot (imágenes, animación, planos). Una vez creada la lista, se solicita la ejecución de todos los comandos. Ejemplo:




En el peor de los casos, si el usuario tiene una mala conexión a internet, o el servidor tiene un mal día, veríamos pasados unos segundos las imágenes con el resultados de las operaciones que le hemos pedido. Y en un buen caso, podríamos ir recibiendo imágenes en directo del GE mientras realiza las ordenes que le hemos pedido.

Estaríamos imitando, con algo de ventaja, a los operadores de un Mars Rover.


Y más o menos así sería el esquema general:



Para aligerar de trabajo y de memoria el hardware del GE, quiero administrar las instrucciones que envie el usuario en el servidor, con PHP. De esta forma, el GE solamente tendrá unas instrucciones predefinidas que ejecutará de una en una conforme se le vaya ordenando.
Continue reading

Un poco de Brainstorming

Hoy he tenido otro día de locos, así que no he podido avanzar nada en el proyecto y tampoco preparar la documentación que quería. Así que voy a ir anotando las ideas que tengo para ir empezando a organizar todo esto.

Servicio:
- Tengo pensado utilizar un Mini PC como servidor web. Así estorbará poco y ahorraré en energía. Le he echado el ojo a un FUTRO C100. Tiene muy buena pinta, hablan muy bien de él y se puede conseguir a buen precio por ebay (lo he pedido hoy mismo).

Datasheet FUTRO C100






- Sobre este PC tengo pensado instalar (como ya he comentado) Ubuntu 10.10 con un servidor LAMP.
- Utilizaré PHP para hablar con los puertos y comunicarme con mi Arduino. De momento será por USB, pero la idea es hacerlo inalámbrico. Con el problema de la retransmisión de video, si es que quiero instalar una cámara en primera o segunda persona. Así que ya veremos.
- Quiero usar una Webcam normal y corriente para servir streaming o imágenes que se actualicen con frecuencia.

Electrónica:
- Cómo placa principal para el GE (Garden Explorer, a partir de ahora) me gustaría utilizar un Arduino 2560. Por la facilidad de programación y la cantidad de accesorios que hay prácticamente plug-and-play. Y la verdad es que los precios no me echan para atrás.

Página oficial Arduino.



- De momento voy a preparar la comunicación a través de USB, aunque más adelante utilizaré un módulo inalámbrico, pero todavía no se de que fecuencia estamos hablando.
- Para controlar los motores utilizaré simples puentes en H o algún módulo que encuentre para controlar motores DC (más adelante viene el porqué de los motores DC).

Mecánica:
- Como base para el primer prototipo voy a utilizar una plataforma mobil con tracción a las cuatro ruedas (me ahorro la dirección, y de esta forma puede girar sobre sí mismo), con muy buena relación calidad/precio.
El DFRobot 4WD Mobile Platform.

Página DFRobots.
Review muy buena.





De momento no necesitamos nada más que montar el Arduino sobre él y conectar los motores al controlador.

Esta sería la descripción de la primera idea a desarrollar.
Continue reading

Día 1 del cuaderno de Bitácora

La parte que se encargará de controlar el funcionamiento del GE será un Arduino. Para el caso he elegido el Arduino Mega 2560, ya que he encontrado muchas herramientas compatibles y la lista de posibilidades es bastante grande.

El problema es que tanto el sistema que voy a utilizar para programarlo, como el servidor que se va a encargar de la comunicación con la placa, son Ubuntu 10.10.
Por lo visto, el nuevo Arduino Mega 2560, ya no trae el FTDI que traía el viejo Arduino Mega. Ahora viene con un Atmega8u2 que al parecer se lleva muy mal con Ubuntu. Así que para poder utilizar el Arduino 0022 con la placa Arduino Mega 2560, hay que flashear el Firmware del Atmega8u2 para que Ubuntu lo reconozca sin problemas.

El problema no es ese. El mayor problema ha sido, que apenas hay documentación para flashear el nuevo Arduino Mega 2560, pero nadie dice nada sobre el Arduino Mega 2560 R2 (que casualmente es el que yo tengo). Ni si quiera en los esquemas de arduino.cc está contemplado este modelo, así que no he podido comparar con ningún esquema que haya encontrado en internet, ni con ninguna foto de las que hay en los escasos tutoriales para flashear (a lo mejor es que googleo muy mal).

Al final no he tenido más que puentear los pines para resetear el Atmega8u2 y hacerlo entrar en "modo dfu" y así poder programarlo.
Como tampoco he encontrado el .hex para flashear el 8u2 de mi placa, me la he tenido que jugar utilizando el que he encontrado para el Mega 2560 normal.

Pero ha funcionado! Tal y como todo el mundo decía, tras flashear el 8u2, todo funciona perfectamente. Ya no he vuelto a tener ningún problema de comunicación, y el Arduino 0022 vuelve a funcionar rapidísimo (como debe ser).

Justo cuando estaba a punto de dejarlo todo y pasarme a Windows, solo para poder programar cómodamente...

A continuación explico con un poco más de detalle los pasos que he seguido para programar mi placa Arduino Mega 2560 R2 con Ubuntu 10.10:

- Para empezar conectamos nuestro Arduino al PC.
- El primer paso sería escribir en la consola "lsusb" y comprobar que ha aparecido un nuevo dispositivo USB. En mi caso lo veríamos así:

Antes de conectar la placa.









Una vez conectado mi Arduino aparece una línea sin descripción.










- Lo siguiente será hacer contacto entre los dos pines rojos que aparecen en la siguiente foto (puedes usar un destornillador de punta plana).
ATENCION: advierto que las instrucciones que estoy dando me han funcionado con la versión Mega 2560 R2. En mi caso, donde en la foto aparecen dos pines bordeados en verde, realmente hay cuatro pines, y no los he necesitado para nada.



- Una vez hecho esto, repetimos el "lsusb" y ahora nos debería aparecer un dispositivo de Atmel.










- Si hasta aquí todo ha ido bien, significa que ya podemos flashear nuestro Atmega8u2 con el nuevo firmware. Podeis descargar el archivo .HEX de >aquí<. En mi caso me ha servido el que ya existía para la Arduino Mega 2560 normal (es el archivo con extensión .hex que empieza con "MEGA").

- Una vez descargado el archivo vamos a flashear nuestro chip utilizando el dfu-programmer. Si no lo habeis utilizado hasta ahora, podeis instalarlo con Synaptic. Pero recomiendo asegurarse de que como mínimo es la versión 0.5.1, ya que he leído que con versiones anteriores han tenido problemas al programar este chip.

- Una vez instalado, podemos utilizar el dfu-programmer directamente en la consola.
Los pasos serían los siguientes:
Entrar en la carpeta en la que se ha descargado el archivo (por defecto Downloads).

cd Downloads/


Ahora borramos el contenido del Atmega8u2.

sudo dfu-programmer at90usb82 erase


Flasheamos nuestro archivo.

sudo dfu-programmer at90usb82 flash (nombre del archivo).hex


Y lo reseteamos.

sudo dfu-programmer at90usb82 reset


- Desenchufamos el cable USB, y en cuanto volvamos a conectarlo tendremos nuestra Arduino lista para jugar.

Llevad mucho cuidado y aseguraos de que os encontrais en la misma situación que yo. No he encontrado nada de documentación sobre este caso, así que no puedo confirmar que sea siempre así. Lo que hagais será bajo vuestra propia responsabilidad. Tened en cuenta que podeis inutilizar vuestro Arduino.

Mucha suerte a todos, y aquí estoy por si puedo ayudar a despejar alguna duda.
Continue reading

Propósito del proyecto

El propósito inicial de este proyecto, es desarrollar un vehículo que pueda ser controlado y supervisado de forma remota.

Hay infinidad de posibilidades, así que de momento me voy a plantear una meta relativamente sencilla.

El proyecto puede terminar en un ROV (Remotely Operated Vehicle) que utilice para divertirme, controlandolo desde la oficina o desde casa de un amigo. Pero también puede terminar en un ROV que pueda usar cualquiera, simplemente entrando en la web y poniendose en la lista de espera.
Se podría usar para pasear por un pequenyo escenario, para resolver puzzles o juegos que serían planteados en la página web.

Otra posibilidad sería, el poder usarlo en el exterior. Pudiendo explorar zonas abiertas, y pasear dentro de un recinto.

Algo así? XD



Hay muchísimas posibilidades, pero de momento, mi meta es poder poner en marcha un servidor web con el que pueda enviar ordenes a un pequenyo vehículo y retransmitir imágenes de su situación.

Mientras sigo pensando en los pasos para el desarollo, he estado buscando las mejores opciones para cada fase del trabajo. Haré un esquema y lo publicaré para recibir vuestras opiniones y depurarlo.

De momento me he enfrentado al primer problema del proyecto.
Continue reading