Jugando con Arduino — Parte III (Domando la fonera)

En la última entrada de esta sección, se explicó como montar una VPN para acceder desde cualquier dispositivo a la Fonera,..

Esta noche intentaremos generar una pequeña interfaz de control para nuestro arduino, para ello utilizaremos la tecnología llamada CGI (Common Gateway Interface) esta tecnología permite incrustar en
una página web un binario, habitualmente estos CGI se utilizan para tareas que requieren acceso a hardware específico de la máquina, como el puerto de serie, webcams,… Habitualmente se sitúan en carpetas especiales en el servidor, suelen tener el nombre de cgi-bin.

CGI Process


Lo que se imprima en la salida estándar de la aplicación, el servidor lo capturará y lo enviará como si fuera una web cualquiera.

Mi primer intento fue utilizar un programita hecho en C, para ello, debemos descargar el entorno de desarrollo de OpenWrt acorde con la versión de firm de nuestra fonera.

Como siempre el primer paso es configurar las dependencias que vamos a necesitar en nuestra máquina:

sudo apt-get install  libncurses5-dev git-core subversion build-essential asciidoc bash bc binutils bzip2 fastjar flex git-core g++ gcc util-linux gawk libgtk2.0-dev intltool  zlib1g-dev make libncurses5-dev libssl-dev patch perl-modules python-gdbm rsync ruby sdcc unzip wget gettext xsltproc zlib1g-dev

El siguiente paso es bajarnos la versión de que necesitemos en mi caso la backfire:

svn co svn://svn.openwrt.org/openwrt/branches/backfire ~/openwrt
cd ~/openwrt
./scripts/feeds update -a
./scripts/feeds install -a

El siguiente paso es compilar openwrt:

make defconfig
make prereq
make menuconfig

En el caso de que nos faltara algún paquete más lo añadimos tal y como hicimos con los paquetes que instalamos en el primer paso.

Al hacer make menuconfig debemos asegurarnos de elegir la arquitectura correspondiente a la fonera, en nuestro caso ala arquitectura (Target System) es una arquitectura Atheros 231x/5312 (MIPS). Además de seleccionar esa opción debemos marcar «Build the OpenWrt based Toolchain». En este punto debemos salir y guardar la configuración.

make -jN

Como es una construcción bastante pesada, para habilitar el «multithreading» de make, debemos añadir el modificador «-jN» y sustituir N por el número de cores de nuestra máquina +1.

Cuando termine de compilar debemos añadir al fichero ~/.bashrc las sigueintes órdenes:

export PATH=$PATH:~/openwrt/staging_dir/toolchain-mips_gcc-4.3.3+cs_uClibc-0.9.30.1/bin/

Hay que recargar las variables de entorno para que los cambios se vean reflejados, para ello lo más sencillo es abrir un nuevo terminal.

Con ello ya tendremos listo el entorno de desarrollo de OpenWrt, pudiendo ejecutar órdenes del estilo:

mips-openwrt-linux-uclibc-gcc cgi-test.c -o cgi-test

Siguiendo con el hilo original de la publicación ahora dejaré un ejemplo de cgi muy simple en C que es el que usé en mi POC.

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <termios.h>
#include <unistd.h>
#include <string.h> /* memset */
#include <unistd.h> /* close */
#include<fcntl.h>
#include <sys/stat.h>

#define MAX_READED_LINE 2

#define DEBUG 1

/** HTML /CGI **/

char* QUERY_STRING;

char header[]="<!DOCTYPE html>\
<head>\
<meta charset='UTF-8' />\
<meta name='viewport' content='width=device-width' />\
<title>Jugando Con arduino -- Parte 4</title>\
\
<link rel='stylesheet' type='text/css' media='all' href='/Styles/cgi.css' />\
</head>\";

char body[]="\
<body class='single single-post postid-508 single-format-standard logged-in admin-bar no-customize-support custom-background singular two-column right-sidebar'>\
<div id='page' class='hfeed'>\
	<header id='branding' role='banner'>\
						<a href='#'>\
									<img src='/Styles/logobdl.png' width='1000' height='0' alt='' />\
							</a>\
						\
			<nav id='access' role='navigation'>\
				<h3 class='assistive-text'>Menú principal</h3>\
								<div class='skip-link'><a class='assistive-text' href='#content' title='Ir al contenido principal'>Ir al contenido principal</a></div>\
				<div class='skip-link'><a class='assistive-text' href='#secondary' title='Ir al contenido secundario'>Ir al contenido secundario</a></div>\
								<div class='menu'><ul><li ><a href='#' title='Inicio'>Inicio</a></li></ul></div>\
			</nav><!-- #access -->\
	</header><!-- #branding -->\
\
\
	<div id='main'>\
\
		<div id='primary'>\
			<div id='content' role='main'>					\
<article id='post-508' class='post-508 post type-post status-publish format-standard hentry category-general'>\
	<header class='entry-header'>\
		<h1 class='entry-title'>Fon2200 — Service</h1><br><h2>Intercambiando datos entre dispositivos</h2>\
			</header><!-- .entry-header -->\
\
	<div class='entry-content'>\
";

//<div class='header'><h1>Fon2200 -- Service</h1><br> <h3>Intercambiando datos entre dispositivos</h3></div>
char form[]="<form action='/cgi-bin/cgi-test' method='get'>\
<select name='selector'>\
  <option value='onX'>Encender</option>\
  <option value='offX'>Apagar</option>\
  <option value='getTempX'>Obtener Temperatura</option>\
  <option value='upX'>Subir</option>\
  <option value= 'downX'>Bajar</option>\
</select>\
<button type='submit'>enviar </button>\
</form>\
<br>";

char footer[]="</div><!-- .entry-content -->\
\
</article><!-- #post-508 -->\
\
				\
			</div><!-- #content -->\
		</div><!-- #primary -->\
\
\
	</div><!-- #main -->\
\
	<footer id=\"colophon\" role=\"contentinfo\">\
		\
	</footer><!-- #colophon -->\
</div><!-- #page -->\
</body>\
</html>";

/*********
-------------------
getValue
------------------
******/

void getValue(char * varname,char** out){

	int trobat=0;
	char *splitted=strtok(QUERY_STRING, "&");
	while	(splitted!=NULL){

		if(strstr(splitted, varname) != NULL){
			trobat=1;
			break;
		}

		splitted=strtok(NULL, "&");
	}
	if(trobat==0){
		*out=NULL;
		return;
	}

	char *value=strtok(splitted, "=");
	value=strtok(NULL, "=");
	//printf(value);
	*out=(char *)malloc(strlen(value)+2);
	memcpy(*out,value,strlen(value)+1);

}

/** SERIAL **/
char portname[] = "/dev/ttyS0";

int set_interface_attribs (int fd, int speed, int parity)
{
        struct termios tty;
        memset (&tty, 0, sizeof tty);
        if (tcgetattr (fd, &tty) != 0)
        {
                printf ("error %d from tcgetattr", errno);
                return -1;
        }

        cfsetospeed (&tty, speed);
        cfsetispeed (&tty, speed);

        tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;     // 8-bit chars
        // disable IGNBRK for mismatched speed tests; otherwise receive break
        // as \000 chars
        tty.c_iflag &= ~IGNBRK;         // ignore break signal
        tty.c_lflag = 0;                // no signaling chars, no echo,
                                        // no canonical processing
        tty.c_oflag = 0;                // no remapping, no delays
        tty.c_cc[VMIN]  = 0;            // read doesn't block
        tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout

        tty.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl

        tty.c_cflag |= (CLOCAL | CREAD);// ignore modem controls,
                                        // enable reading
        tty.c_cflag &= ~(PARENB | PARODD);      // shut off parity
        tty.c_cflag |= parity;
        tty.c_cflag &= ~CSTOPB;
        tty.c_cflag &= ~CRTSCTS;

        if (tcsetattr (fd, TCSANOW, &tty) != 0)
        {
                printf ("error %d from tcsetattr", errno);
                return -1;
        }
        return 0;
}

void set_blocking (int fd, int should_block)
{
        struct termios tty;
        memset ((char*)&tty, 0, sizeof tty);
        if (tcgetattr (fd, &tty) != 0)
        {
                printf ("error %d from tggetattr", errno);
                return;
        }

        tty.c_cc[VMIN]  = should_block ? 1 : 0;
        tty.c_cc[VTIME] = 5;            // 1 seconds read timeout

        if (tcsetattr (fd, TCSANOW, &tty) != 0)
                printf ("error %d setting term attributes", errno);
}

/******************** MAIN ********************/

int main(void){
	//setenv("QUERY_STRING","selector=onX",1);
	int counter=0;
	int readed=0;
	char buf [100];
	char* cmd;
	QUERY_STRING = getenv("QUERY_STRING");

	printf("Content-type: text/html\n\n");
	printf(header);
	printf(body);
	printf(form);

	if(strlen(QUERY_STRING)>0){
		//printf(QUERY_STRING);
		getValue("selector",&cmd);

		if(cmd!=NULL){
			if(DEBUG){
				printf(cmd);
			}
			int fd = open (portname, O_RDWR | O_NOCTTY | O_SYNC);
			if (fd < 0)
			{
				printf ("error %d opening %s: %s", errno, portname, strerror (errno));
				return;
			}

			set_interface_attribs (fd, B38400, 0);  // set speed to 115,200 bps, 8n1 (no parity)
			set_blocking (fd, 0);                // set no blocking

			write (fd, cmd, strlen(cmd));           // send 7 character greeting
			memset(buf,0,sizeof(buf));
			while(strstr(buf, "res:") == NULL && counter<MAX_READED_LINE){

				readed += read (fd, buf, sizeof(buf));  // read up to 100 characters if ready to read*/
				counter++;
				//printf(buf);
				if(DEBUG){
					printf("Reading pass %d --> result: %s <br>",counter,buf);
				}

			}
			printf("<p class='serialResult'>Se ha obtenido del puerto de serie: <span id='result'> %s</span></p>",buf);
			tcflush(fd,TCIOFLUSH);
			close(fd);
		}

	}

	printf(footer);

}

Por alguna extraña razón el puerto de serie tras un reinicio de la fonera dejó de enviarme datos por este método…

Por ello decidí usar lo que más a mano tenía.. ‘ash’.. Si amigos una web en ASH!!!

Para poder ejecutar el siguiente ejemplo debemos tener instalado el paquete coreutils-stty:

opkg update
opkg install coreutils-stty

Cuando tengamos el paquete anterior instalado en la fonera, podremos ejecutar el siguiente script en nuestra fonera, como observáis hay sintaxi un poco extranya. Es por culpa del intérprete ash es un poco especialito, no acepta muchas expresiones propias de bash, dificultando la programación..

#!/bin/sh
serialPort="/dev/ttyS0"
tmpSerial="/tmp/ttyS0.tmp"
HTML=""

 getQS()
{

NAME=$1
echo  $(echo $QUERY_STRING | sed 's/\&/\n/g' | grep "$NAME=" | cut -f2 -d '=')
}

sendCmd(){
	SELECTOR=$1
        rm -f $tmpSerial
       # cat < $serialPort >> $tmpSerial &  pid=$! ## Guardam la info del port serie...
	(stty raw; cat > $tmpSerial) < /dev/ttyS0 & pid=$!
      	  sleep 1
         echo $SELECTOR > $serialPort    #Executam ordre..
       # echo "echo $SELECTOR > $serialPort "
       #  echo  "encendiendo tele";
        sleep 1
        kill $pid

        result=$(cat $tmpSerial  | grep "res:" | tail -1);
        echo $result
}

header(){
	cat<<EOF
<!DOCTYPE html>\n
<head>\n
<meta charset="UTF-8" />\n
<meta name="viewport" content="width=device-width" />\n
<title>Jugando Con arduino -- Parte 4</title>\n

<link rel="stylesheet" type="text/css" media="all" href="/Styles/cgi.css" />\n
</head>\n

<body class="single single-post postid-508 single-format-standard logged-in admin-bar no-customize-support custom-background singular two-column right-sidebar" onload="extractResult()">\n
<div id="page" class="hfeed">\n
	<header id="branding" role="banner">\n
						<a href="#">\n
									<img src="/Styles/logobdl.png" width="1000" height="0" alt="" />\n
							</a>\n

			<nav id="access" role="navigation">\n
				<h3 class="assistive-text">Menú principal</h3>\n
								<div class="skip-link"><a class="assistive-text" href="#content" title="Ir al contenido principal">Ir al contenido principal</a></div>\n
				<div class="skip-link"><a class="assistive-text" href="#secondary" title="Ir al contenido secundario">Ir al contenido secundario</a></div>\n
								<div class="menu"><ul><li ><a href="#" title="Inicio">Inicio</a></li></ul></div>\n
			</nav><!-- #access -->
	</header><!-- #branding -->\n

	<div id="main">\n

		<div id="primary">\n
			<div id="content" role="main">	\n
<article id="post-508" class="post-508 post type-post status-publish format-standard hentry category-general">\n
	<header class="entry-header">\n
		<h1 class="entry-title">Fon2200 — Service</h1><br><h2>Intercambiando datos entre dispositivos</h2>\n
			</header><!-- .entry-header -->\n

	<div class="entry-content">\n

EOF
}

footer(){

cat << EOF
</div><!-- .entry-content -->\n

</article><!-- #post-508 -->\n

			</div><!-- #content -->\n
		</div><!-- #primary -->\n

	</div><!-- #main -->\n

	<footer id="colophon" role="contentinfo">\n

	</footer><!-- #colophon -->\n
</div><!-- #page -->\n
<script>\n

function extractResult(){\n

	var res=document.getElementById("result");\n

	if(res==null){\n

	return;\n
	}

	if (res.innerText==''){\n

	alert("Algo fue mal entre la conexión fon-arduino.");\n
	return;\n
	}
	var txt=res.innerText.split(":")[1].replace(';','').replace('\\\\n','');
	if(isFloat(txt)){\n
		txt+='º';	\n
	}\n

	alert("El resultado de la ejecución fue:"+txt);\n

}\n

function isFloat(value){\n
	if (!isNaN(value) && value.toString().indexOf('.') != -1)\n
	{\n
	   return true;\n
	}\n
	return false;	\n
}\n

</script>\n

</body>\n
</html>\n

EOF

}

HTML="Content-type: text/html"
HTML="$HTML \n\n"
HTML="$HTML $(header)\n"
HTML="$HTML <form action='/cgi-bin/test.sh' method='get'>\n"
HTML="$HTML <select name=\"selector\">\n
  <option value=\"onX\">Encender</option>\n
  <option value=\"offX\">Apagar</option>\n
  <option value=\"getTempX\">Obtener Temperatura</option>\n
  <option value=\"upX\">Subir</option>\n
  <option value= \"downX\">Bajar</option>\n
</select>\n"

HTML="$HTML <button type=\"submit\">enviar </button>\n"
HTML="$HTML </form>\n"
HTML="$HTML <br>\n"

if [  "z${QUERY_STRING}" != "z" ];then
SELECTOR=$(getQS 'selector')
#echo $SELECTOR

case $SELECTOR in
 "onX")
	#echo $SELECTOR
	result=$(sendCmd $SELECTOR)
	#echo $result

   ;;
 "offX")
	#echo $SELECTOR
	result=$(sendCmd $SELECTOR)
	#echo $result

   ;;
 "upX")
	#echo $SELECTOR
	result=$(sendCmd $SELECTOR)
	#echo $result

   ;;
 "downX")
	#echo $SELECTOR
	result=$(sendCmd $SELECTOR)
	#echo $result

   ;;
 "getTempX")
	#echo $SELECTOR
	result=$(sendCmd $SELECTOR)
        #echo $result
  ;;

 *)
	result= "Sin definir";
 ;;
esac
HTML="$HTML <p style='display:none' id='result'>$result</p>\n"

fi

HTML="$HTML  <p id='env' style='display:none'>\n"
HTML="$HTML $(env)\n"
HTML="$HTML </p>\n"
HTML="$HTML $(footer)\n"

echo -e $HTML

Como podéis observar la web es muy parecida al template de bitsdelocos… los que me conocen saben que no tengo imaginación…

Otros diréis podrías haberlo separado por capas.. la capa servidora, la capa de datos y la de presentación… cierto es.. pero lo pensé tarde =)

Aquí os muestro unas imágenes de como quedó el invento:
Servicio Fon2200Listado de acciones disponibles

2 comentarios en “Jugando con Arduino — Parte III (Domando la fonera)

  1. hola, tenia en mente un proyecto similar, pero no se por donde comenzar , y soy bastante nuevo en el asunto. se puede hacer esto con cualquier router con openwrt?

    • Sí, no deberías tener problemas para realizar este tipo de proyectos. Si necesitas ayuda, pídela y en lo que pueda te echaré un cable.

      Es más si consigues hacerlo y quieres que lo publique dame un toque.

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.