Hacía tiempo que no volvía a escribir sobre la cámara ip Zaapa CIPRW. Escribí dos artículos: Uno sobre la descripción de la cámara en septiembre de 2008 y otro sobre cómo programarla en .NET en Agosto de 2009.
Lo cierto es que desde entonces dejé abandonado el tema y por necesidades he tenido que retomarlo para poder interactuar con ella. Poco tiempo después de escribir el último artículo se publicó en la página de Gadget Victims información sobre un nuevo firmware para las cámaras Foscam FI908W (La de Zaapa es la FI8901W) en septiembre de 2009 donde entre otras cosas se habla de la documentación oficial de FOSCAM para sus cámaras IP. Lo mejor de esto es que en esta documentación hay dos textos que pueden servirnos a los programadores para interactuar definitivamente con la cámara Zaapa y a los usuarios para poder ver sus cámaras en navegadores como Firefox, Chrome, Safari, etc.
Se trata del IPCAM CGI SDK 2.1 y del IPCAM Protocol. El primero es un documento PDF donde se explica cómo haciendo peticiones HTTP se puede descargar la imagen de la cámara y enviar ordenes a esta. El segundo es un documento de Word donde se explica el protocolo de la cámara para comunicarse con ella mediante socket. En este artículo me centraré en el primero por su facilidad de uso.
En el primer documento se explica qué peticiones hay que hacer para interactuar con la cámara. Se trata de acceder a páginas web CGI pasándole normalmente los parámetros mediante el método GET, que es como en realidad nosotros hacemos las llamadas desde un navegador web normalmente.
Antes de empezar a probar hay que cerciorarse de que la cámara Zaapa tiene el último firmware y el último Embeded Web UI. Si en la sección Device Info del panel del administrador son distintos de 11.4.1.40 y 2.0.0.16 respectivamente, hay que actualizar. En la página del producto se pueden descargar pero hay que introducir el número de serie que aparece como código de barras en la parte inferior de la cámara. Una vez que empieza la descarga empieza lo gracioso y es que veremos algo raro en el fichero que descargamos pues no tiene extensión y que no sirve para actualizar ya que no es un .bin. Pues bien el fichero es un archivo .rar por lo que hay que renombrarlo, ponerle esa extensión y ya podemos abrirlo. Dentro de este veremos dos ficheros .bin y aquí esta lo segundo más gracioso ¡¡¡ están al revés !!! Embeded Web UI 11.4.1.40.bin tendría que ser Firmware 11.4.1.40.bin y Firmware 2.0.0.16.bin tendría que ser Embeded Web UI2.0.0.16.bin. Una vez arreglado ese desaguisado ya podemos actualizarlo desde la sección Upgrade Device Firmware del panel de administrador y actualizar el firmware primero y el Web UI después (en ambos casos se reinicia la cámara).
Hay unos cuantos CGI y lo mejor es leerse la documentación, pero voy a explicar 3 de ellos que me parecen fundamentales:
El primero de ellos es el snapshot.cgi. Sirve para capturar una imagen estática de la cámara. Si ponemos en un navegador web http://ip de la cámara/snapshot.cgi nos pedirá un usuario y una contraseña. Cuando la hayamos introducido veremos la imagen jpg. Podemos evitar que salga el recuadro que nos pide el usuario y la contraseña simplemente añadiendo los parámetros user y pwd a la url de la siguiente forma http://ip de la cámara/snapshot.cgi?user=usuario&pwd=contraseña para que ya directamente nos muestre la imagen. Tiene otro parámetro llamado next_url para indicar el nombre del fichero pero no lo veo útil.
El segundo de ellos son en realidad dos: videostream.cgi y videostream.asf. Sirven para mostrar un flujo continuo de imagenes a modo de vídeo. El que tiene extensión .cgi envía imágenes jpg sucesivas (ideal para verlas con una navegador), el que tiene extensión asf envía las imágenes con el formato Advanced Streaming Format (ideal para verlas con el VLC o con el MPlayer). Si ponemos en un navegador web http://ip de la cámara/videostream.cgi?user=usuario&pwd=contraseña podemos ver el flujo continuo de lo que la cámara está enfocando en ese momento. Tiene otro parámetro llamado resolution para indicar con un 8 o un 32 si queremos que la imagen tenga de tamaño 320×240 o 640×480 respectivamente.
El tecero de ellos es el decoder_control.cgi. Sirve para interactuar con la cámara. Si ponemos en el navegador web http://ip de la cámara/videostream.cgi?user=usuario&pwd=contraseña&command=comando donde comando sea un número que indica qué hacer a la cámara (0 = empezar a mover hacia arriba, 1 = parar de mover hacia arriba, 2 = empezar a mover hacia abajo, etc) veremos como se mueve esta.
Finalmente he creado una página HTML para probar los dos últimos CGIs que he explicado. La página muestra la sucesión de imágenes de la cámara y tiene unos botones que permiten mover la cámara. Para adecuarlo a la vuestra sólo teneis que cambiar en la sección javascript el usuario, la clave y la ruta a vuestra cámara:
El funcionamiento es muy sencillo. Una etiqueta img muestra el CGI videostream.cgi. Las celdas de la tabla tienen los eventos onmousedown y onmouseup para detectar cuando se pulsan y cargar en el marco oculto el CGI decoder_control.cgi con la orden correspondiente. Así es como se ve funcionando en un MAC y Safari:
Y de paso pongo otro vídeo de cómo se puede usar esta cámara con un móvil con las múltiples aplicaciones que hay en android market y en app store simplemente eligiendo en estas como marca de cámara la FOSCAM:
Hacía tiempo que no me ponía a mi mismo un reto de programación y el otro día viendo en la tele el programa cifras y letras me animé a hacer una aplicación que resolviese la sección de cifras. Primero me leí las normas y luego empecé a pensar cómo resolver este tipo de problema. Buscando por Internet encontré estos artículos: (uno, dos y tres) donde de 30.965.760 combinaciones posibles entre los 6 números y sus cuatro operaciones se reduce a 488.642 combinaciones. Además me encontré con dos páginas que resuelven el problema on-line: esta y esta (ambas usando la técnica de backtracking).
Como mi objetivo era hacerlo mediante fuerza bruta, la técnica de backtracking me pareció lo mejor para abordar el problema. Se crea un árbol donde cada nodo contendrá los números con los que se operan, la operación que ha generado ese nodo y el resultado de la misma (excepto el primer nodo que sólo contiene el conjunto de números original).
Vamos combinando uno por uno todos los números con todas las operaciones para encontrar el resultado que buscamos. El orden de las operaciones es suma, resta, multiplicación y división. Como esto puede generar una ingente cantidad de cálculos, podemos podar (acotar) el árbol para reducir estos y el tiempo empleado. Esto se consigue eliminando aquellos casos que no deben darse: En la resta que el resultado sea 0, en la división que el divisor sea 1 o que el resto sea distinto de 0 y que en la multiplicación uno de los factores sea 1. Para evitar números negativos en la resta o que el divsor sea mayor que el dividendo ponemos primero el mayor y después el menor en la operación.
A medida que vamos avanzando en la profundidad del árbol, el conjunto de números con los que operar se irá reduciendo ya que cada pareja de números se convertirá en uno por la operación matemática que se les aplique, siendo esto así recursivamente hasta que sólo haya un número, momento en el cual si el resultado no coincide con el esperado, se retrocede un nodo y se continúa con las siguientes combinaciones.
Para entenderlo mejor un gráfico donde dado un conjunto de tres números (1, 2 y 3) debemos operar con ellos hasta que obtengamos 7 como resultado:
En el ejemplo después de buscar varias combinaciones entre sumas y restas (puntos suspensivos en el gráfico) hemos llegado a las combinaciones de multiplicaciones. Existen tres combinaciones de multiplicaciones: Op: 1 * 2 (que no he puesto en el gráfico por simplificarlo), Op: 1 * 3 y Op: 2 * 3.
En el nodo de la multiplicación de 1 * 3 el resultado (Res:) es 3 y no 7 como andamos buscando por lo que hay que seguir calculando. El conjunto de números de este nodo se ha reducido de Nu: 1, 2, 3 a Nu: 2, 3.
A continuación hay que crear otro nodo con la suma de los únicos números que quedan: 2 + 3, pero el resultado es 5 con lo que volvemos al nodo anterior.
Vamos a hacer la resta: 3-2, pero sigue sin servirnos el resultado, por tanto volvemos al nodo anterior.
Hacemos la multiplicación: 2 * 3, pero seguimos igual, por lo que nos vamos al nodo anterior.
La división se descarta porque 3 / 2 no dá como resto 0, con lo que no se crea ese nodo y se vuelve al anterior (que en este ejemplo es el principal).
Continuamos con la creación del nodo de la multiplicación de 2 * 3 donde el resultado (Res:) es 6 y no 7 como andamos buscando por lo que hay que seguir calculando. El conjunto de números de este nodo se ha reducido de Nu: 1, 2, 3 a Nu: 1, 6.
Hay que crear otro nodo con la suma de los únicos números que quedan: 1 + 6, y como el resultado es 7, que es el que buscábamos, ya no hacemos ninguna operación más y vamos retrocediendo por todo el árbol (que os recuerdo que se ha construido mediante una función recursiva) hasta salir de la función que lo ha generado.
El nivel de profundidad de los árboles depende de la cantidad de números inicial. Si son un conjunto de 3 números la profundidad será de 3 niveles, si es de 6 pues … ya sabeis la respuesta 🙂
Como se trata de una estructura de árbol, cada nodo debe tener un puntero al siguiente nodo para trazar un camino desde el nodo inicial hasta el nodo que contiene el resultado que buscamos (en el cual el puntero estará vacío). Dado que hemos aplicado la técnica de backtracking los nodos que previamente hayamos calculado y no pertenezcan a ese camino desaparecerán porque no nos sirven. Finalmente mediante un bucle recorreremos todos los nodos del camino mostrando por pantalla la operación que lo ha creado hasta el nodo final. Así en el ejemplo quedaría:
1
2
3*2=6
6+1=7
Sin embargo en el juego de cifras y letras si no se encuentra el número exacto se puntúa el número que más se acerque a este. La problemática aquí es que con el bactracking, si no se encuentra el número exacto, el camino que se habrá generado cuando retorne la función es el de la última operación, que con toda probabilidad no será el camino hacia el número que más se aproxime al original.
En este caso tenemos dos posibles formas de solucionarlo:
A medida que vamos generando los nodos debemos comparar el resultado con el número que buscamos, si se acerca más que el anterior valor que hayamos comparado guardamos este resultado como el número que más se aproxima al buscado. Después cuando haya salido de la función y no se haya encontrado el exacto, se vuelve a llamar a esta misma pero buscando en esta ocasión el resultado aproximado (ya que tenemos la certeza de que se puede calcular) obteniendo así el camino hasta llegar al que más se acerca.
El problema de la solución anterior es que tenemos que llamar dos veces a la función que genera el árbol: una para buscar el exacto y otra para buscar el aproximado. Lo ideal es ir guardando un camino alternativo hacia el número aproximado, para que, en caso de no hallar el exacto, recorrer el camino alternativo mediante un bucle para mostrar las operaciones que obtengan el número aproximado. Todo desde la misma llamada a la función. Esto provoca que también se necesite un puntero al nodo anterior.
Aquí dejo el código fuente en C# que pone en práctica todo lo comentado. Se trata de una aplicación de consola donde como parámetros se le pasa todo el conjunto de números separados por espacio y como último número el resultado que se desea averiguar.
Program.cs:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Collections;
usingSystem.Text;
namespaceCifras
{
classProgram
{
staticintMain(string[]args)
{
// Ncesitamos como mínimo dos números para operar y un resultado
if(args.Length<3)
{
Console.Write("La sintaxis es "+System.AppDomain.CurrentDomain.FriendlyName+" <número 1>...<número n> <número buscado>");
return1;
}
Console.Write("Set de números: ");
ArrayList posibilidades=newArrayList();
intindice=0;
// Creamos el conjunto de números a partir de la línea de comandos.
El hecho de que Microchip saque al mercado PICs de 32 bit con encapsulado DIP va a revolucionar el mundo de los aficionados a la electrónica. Un simple chip con 28 pines tan potente como el primer 386 de intel (salvando las arquitecturas Harvard y von Neumann) que podemos poner en nuestras protoboards. Puede correr hasta 80 Mhz., 512KB de Flash (donde se almacena el código), 128KB. de SRAM (donde se almacenan los datos) y la posibilidad de conectar con periféricos mediante DMA.
Se pueden pedir samples, pero pueden tardar 42 días en enviarlos.
Cuando queremos crear un robot que distinga el blanco del negro de una superficie tenemos que leer la posición en la que se encuentra con varios sensores infrarrojos.
To make a robot that distinguishes a black area between a white one, we need to know the position of the robot by reading the information of several infrared sensors.
Muchas veces los microcontroladores que usamos para estos robots tienen un número muy ajustado de pines de entrada y salida, y si tenemos que usar uno de estos por cada sensor infrarrojo que utilicemos vamos a tener problemas para añadirle funcionalidades a nuestro robot: Comunicaciones serie, acelerómetros, giroscopios, i2c, sensores de distancia, bumpers, botones, motores, leds, etc.
Many times the microcontrollers used for these robots have a very limited number of in/out pins, so if we have to use one of those for each infrared sensor, we will find problems to add functionalities to our robot as Serial Communications, accelerometers, gyroscopes, i2c, distance sensors, bumpers, buttons, motors, LEDs, etc..
En los robots rastreadores se usan dos sensores infrarrojos como mínimo, en los de sumo se usan 4, en los velocistas suelen ser 6 sensores o más. Todo esto provoca que tengamos que «desaprovechar» pines de nuestro micro para leer los sensores. Sin embargo hay una forma de leer varios sensores de infrarrojos usando sólo un pin analógico del microcontrolador.
Tracker robots at least use two infrared sensors; sumo robot 4 and line follower robot tend to have 6 or more sensors. All this causes the «waste» of the pins of our micro to read sensors. However, there is a way of reading several infrared sensors using only one analog pin microcontroller.
La idea proviene de este artículo, donde se explica cómo se puede leer un teclado matricial de 4 filas x 3 columnas con un sólo pin analógico de un microcontrolador PIC. El objetivo es usar varias resistencias para que, dependiendo de la tecla que se pulse, el pin del micro reciba una tensión distinta y poder discernir cuál es la que se ha pulsado. Aquí tendremos un inversor Schmitt trigger en cuyas salidas pondremos resistencias en serie de distintos valores unidas a un punto en común. Desde ese punto se medirá el valor de la tensión, que será distinto dependiendo del sensor activo, pudiendo conocer así la posición del robot.
The idea comes from this article, which explains how to read a keypad matrix of 4 rows x 3 columns with only one analog pin of the PIC microcontroller. The objective is to use several resistors for, depending on which key is pressed, the micro pin receives a different voltage discerning this way which key has been pressed. At this point, we will have a Schmitt trigger inverter whose output have resistors connected in series of different values attached to a common point. From this point the voltage value is measured, which will be different depending on the active sensor, being able to know the position of the robot.
Primera ventaja: Tener varios sensores CNY70 o un array QTR-8A ya no es un problema porque podemos leer todos los valores con un sólo pin de nuestro microcontrolador y dejar el resto para otras tareas.
First advantage: Having multiple CNY70 sensors or a QTR-8A array is no longer a problem because we can read the values with a single pin of our microcontroller and leave the rest for other tasks.
Segunda ventaja: Por cada sensor que se leía con un pin analógico se provocaba una pausa, ya que la lectura de un ADC tarda un tiempo en hacerse, multiplicado por el número de sensores. Con esta solución sólo se hace una lectura.
Second advantage: Each sensor read with an analog pin causes a pause, since the reading of an ADC takes a while to execute, and this pause must be multiplied by the number of sensors. Using this solution takes only one reading.
Tercera ventaja: Como vamos a usar un inversor schmitt trigger y este sólo nos da una señal alta (5 v.) o baja (0 v.) en sus salidas, con las resistencias siempre vamos a obtener los mismos resultados de tensión aunque cambiemos de entorno (pistas de competición o caseras con distinta iluminación), por tanto no hay que calibrar.
Third advantage: As it is use a Schmitt trigger inverter which only gives a high level (5 V) or low one (0 V) in their outputs, the resistors are always going to get the same voltage if you change the environment (race tracks with different lighting or home), so there is no need to calibrate.
Cuarta ventaja: Con las resistencias correctas sólo es necesario tener un ADC de 8 bits (0-255) para medir el voltaje (en tramos de 0,02 V. aproximadamente) con una referencia de 5 V.
Fourth advantage: With the proper resistors it is only necessary an ADC of 8 bits (0-255) to measure the voltage (0.02 V steps approximately) with a 5 V reference.
Voy a poner como ejemplo 8 sensores infrarrojos que leen una hoja que tiene fondo blanco (devuelven valor cercano a 0) y la línea de color negro (devuelven un voltaje mayor). Como inversor schmitt trigger podemos usar un 40106 o un 74HCT14 que tienen 6 entradas/salidas. Como se necesitan 2 entradas más para alcanzar las 8 hay que poner otro chip inversor (recomiendo distribuirlos en 4 + 4 en vez de 6 + 2). También necesitaremos 8 diodos y 9 resistencias.
Let me give an example of 8 infrared sensors that distinguish a white background (return value close to 0) and the black line on it (that return a higher voltage). As the Schmitt trigger inverter it can be used a 40106 or 74HCT14 which has 6 inputs / outputs. Since it takes 2 more entries to reach the 8 sensor, another chip inverter is necessary (I recommend distribute 4 + 4 instead of 6 + 2). We also need 8 diodes and 9 resistors.
La salida de cada CNY70 o de cada elemento del array QTR-8A va dirigido a una de las entradas de uno de los inversores.
The output of each CNY70 or each element of the QTR-8A array is routed to one input of one of the inverters.
La salida correspondiente de ese inversor va a un diodo enfrentado. Esto es necesario ya que al juntar al final todas las salidas evitamos que si una salida esta a 5 v. y otra a 0 v. se derive la corriente entre ellas.
The corresponding output of each inverter is routed to a faced diode. This is necessary since it prevents the pass of current among outputs in different states (one output in 5V and another at 0V).
Después del diodo viene la resistencia, que dependiendo del valor que tenga hará que la tensión caiga más o menos. El valor de la resistencia debe ser único con respecto al de las otras para que la caida de tensión sea diferente.
After the diode, it is placed the resistor, which depending on its value, the voltage will decrease more or less. Each resistor value must be unique in the set so the final voltage was different.
Finalmente se unen todas las salidas en otra resistencia para crear lo que se denomina un divisor de tensión. En ese punto se debe poner la línea que une al pin analógico del microcontrolador para que este pueda medir el voltaje total y diferenciar qué sensor o sensores están activos. Esta resistencia final que une al resto va a alimentación (Vcc). Esta configuración esta diseñada para poder leer el fondo blanco con una línea negra siempre y cuando los sensores nos den un valor cercano a 0 para cuando leen el fondo blanco y un valor mucho mayor para cuando leen la línea negra. Si se necesita cambiar este comportamiento, se puede conectar la resistencia final a masa e invertir los diodos.
Finally, all outputs are joined together in another resistor to create what is called a voltage divider. In that point you may put the line connected to the microcontroller’s analog pin so the total voltage can be measured and distinguish which sensor or sensors are active. This final resistor that joins the other ones must be connected to Vcc. This configuration is designed to read a black line on the white background as long as the sensors give us a value close to 0 when reading the white background and a much greater value when reading the black line. If you need to change this behavior, you can connect the ending resistor to ground and reverse the diodes.
Ahora que ya recibimos distintas tensiones en el pin analógico de nuestro microcontrolador dependiendo de los sensores que estén activos o no, debemos plantearnos cuántas medidas posibles podemos tener. Como he puesto un ejemplo de un velocista podemos tener los siguientes casos:
Todos los sensores leen el fondo blanco.
Un sensor lee la línea negra y el resto el fondo blanco.
Dos sensores leen la línea negra y el resto el fondo blanco.
Now that we got different voltages in the analog pin of our microcontroller depending on the sensors that are active or not, we must consider how many possible measures can we have. As continuing with the example of the line follower, we have the following cases:
All sensors read white background.
One sensor reads the black line and the rest, white background.
Two sensors read the black line and the rest, white background.
Así pues según la combinatoria, debemos multiplicar el número de sensores por 2. En total tenemos 16 medidas posibles (y diferentes) para todos los estados que pueden tener nuestros sensores. Pongo un esquema con los distintos estados, el voltaje resultante según una simulación y su valor de ADC de 8 bits:
Using Combinatory, we must multiply the number of sensors by 2. In total there are 16 possible (and different) measures for all states which can have in our sensors. I put a scheme with the states, the resulting voltage according to a simulation and its value in 8-bit ADC:
Aquí dejo un vídeo de una simulación en proteus sobre la activación de varios sensores (verlo en HD y a pantalla completa):
Here I show a video of a simulation in proteus on the activation of multiple sensors (see this in HD and full screen):
Para uno de sumo ya no valdría el cálculo anterior ya que habría 7 combinaciones posibles con sus 4 sensores y el fondo es negro con una línea blanca:
For a sumo robot the previous situation is no longer valid as it would be 7 combinations with 4 sensors and the background is black with a white line:
Ahora sólo quedaría medir una primera vez los distintos valores de los sensores y luego programar en nuestro microcontrolador una tabla con esos valores que nos servirá para siempre. Por supuesto como los valores de las resistencias tienen un margen de error y la caída de tensión de los diodos no suele ser siempre el mismo, sólo podremos hablar de valores orientativos y únicamente podemos sacar los valores correctos midiéndolos directamente con un polímetro o que el microcontrolador nos lo diga a través del puerto serie. Yo he probado con valores consecutivos de resistencias comerciales y me ha funcionado bien.
Last action to take is to measure the different sensor values and then program a table in our microcontroller with those values that will be correct for us forever. Of course, the values of the resistors have a margin of error and the voltage drop across the diodes is not usually the same: we are talking only about approximate values and we can only get the correct values by measuring directly with a multimeter or with the microcontroller and sending it trough the serial port. I’ve tried commercial consecutive values of resistors and it has worked well.
Finalmente comentar que si nuestro micro no tiene pin analógico pero si tres pines digitales libres podemos usar un ADC0831.
Finally, I would like to remark that if our micro does not have an analog pin but three digital pins, we can use an ADC0831.
Gracias a Maiki por la traducción / Thanks to Maiki for translation
Todo el mundo ha hablado de la reciente muerte de Steve Jobs. Sin embargo también recientemente ha fallecido una persona que ha contribuido enormemente al desarrollo de la informática: Dennis Ritchie. Este hombre fue el creador junto a Ken Thompson del sistema operativo Unix (en el cual está basado GNU/Linux de Richard Stallman y Linus Torvalds) pero su mayor aportación fue crear el lenguaje de programación C, quizá el más usado de la historia y del que derivan otros tantos como objective-c, java y c#. Hasta siempre Dennis.
Ya han publicado el vídeo de la charla de telemetría que di en la OSHWCON 2011.
Toda la documentación de mi charla (presentación, código fuente, esquemáticos, etc.) lo podeis descargar aquí.
Agradecer a los organizadores su tiempo, esfuerzo y ganas por sacar adelante algo tan novedoso y pionero. También dignos de mención son los ponentes que de forma altruista hemos hecho realidad este fantástico evento. Por supuesto no nos olvidemos de los patrocinadores que han permitido que este evento tuviese un nivel alto y de calidad.
Ahora que están de moda las placas que contienen un linux embebido como las beagleboard, las raspberry pi o las foneras, he creido interesante escribir un artículo en el cual se explica cómo ejecutar una tarea por tiempo indefinido desde que arranca la placa y sin necesidad de acceder por shell a la misma.
Presupongo que tienes el compilador cruzado que genera ejecutables para la plataforma de la placa y que tienes acceso a ella mediante shell con el usuario root.
Nuestro objetivo es sencillo, crear lo que se denomina en entornos linux y unix un demonio (en windows es un servicio) lo más sencillo posible. Un demonio no es nada más que una aplicación que se ejecuta en segundo plano y no muestra directamente datos al usuario ni este puede interactuar con ella.
Como queremos que el ejemplo sea bastante sencillo nuestro demonio simplemente va a escribir en un fichero una cadena cada cierto tiempo. Vosotros podreis modificarlo para que haga lo que querais (leer y escribir datos en un puerto serie, analizar imágenes de una webcam, manejar GPIO, crear un servicio para internet, etc).
Scripts de demonios:
Normalmente cuando se arranca un sistema linux, después de cargar el kernel se ejecuta el proceso init, que es el proceso padre del que dependerán el resto de procesos que se ejecuten en el sistema. Este proceso lee el fichero /etc/inittab para saber entre otras cosas en qué nivel de arranque (runlevel de 1 a 5) debe iniciar el sistema.
Dependiendo de ese nivel de arranque se ejecutarán unos scripts ubicados en la carpeta correspondiente. Así por ejemplo si el nivel es 3, los scripts de la carpeta /etc/rc3.d que empiecen por S (Start) se ejecutarían al arrancar. Estos scripts tienen un número que indica en qué orden se ejecutan (se puede repetir el número).
Pero… un momento… ¡¡¡ Si estos ficheros son en realidad son enlaces simbólicos !!!.
Efectivamente, en realidad son accesos directos que apuntan al directorio /etc/init.d donde realmente están los scripts para los demonios. El proceso init lee de la carpeta /etc/rc3.d los enlaces que empiezan por S y ejecuta el script al que apunta con el parámetro start. Igualmente cuando se apaga o se reinicia el sistema el proceso init lee de la carpeta /etc/rc0.d o /etc/rc6.d respectivamente los enlaces que empiezan por K y ejecuta el script al que apunta con el parámetro stop.
Los scripts que se encuentran en la carpeta /etc/init.d son en realidad scripts shell que dependiendo del parámetro que se le pase (start, stop, reload, etc) ejecuta el comando necesario para lo que se requiere. Ni que decir tiene que igualmente nosotros podemos ejecutar por nuestra cuenta el script con los parámetros mencionados para parar o arrancar el demonio desde una consola.
Por tanto vamos a crear un sencillo script que arranque o pare nuestro demonio. Lo crearemos dentro del directorio /etc/init.d y lo llamaremos nohacenada con el siguiente contenido:
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/sh
case"$1"in
start)
/usr/sbin/nohacenada
;;
stop)
test-e/var/run/nohacenada.pid||exit2
kill`cat/var/run/nohacenada.pid`
;;
*)
echo"El uso es: $0 {start|stop}"
exit1
;;
esac
exit0
Este ejemplo muestra un sencillo script donde se espera como parámetro una cadena start o stop. Si es start simplemente arranca la aplicación. Si es stop comprueba si existe el fichero /var/run/nohacenada.pid para saber si la aplicación está corriendo, después lee su contenido, que es el identificador del proceso, para posteriormente mandarle una señal kill y que este pueda cerrarse por si sóla limpiamente. Si no es ninguno de los dos parámetros anteriores es que el usuario intentó ejecutar el script por su cuenta sin poner start o stop como parámetro.
El script debe tener permisos 755 y pertenecer a root por lo que debemos dárselo de esta forma desde una consola shell con el usuario root:
Shell
1
2
chmod755/etc/init.d/nohacenada
chownroot:root/etc/init.d/nohacenada
Además hay que crear los enlaces simbólicos en el directorio correspondiente para que init sepa que tiene que arrancarlo (niveles 2, 3, 4 y 5) y pararlo (niveles 0, 1 y 6):
o si te lo permite el linux que tienes instalado, simplemente:
Shell
1
update-rc.dnohacenada defaults76
Con estos pasos ya hemos terminado nuestro script para que el proceso init pueda arrancar o parar nuestro demonio.
El demonio:
Para crear nuestro demonio usaremos el lenguaje C, que es el lenguaje estandar de linux. Nuestra aplicación se convertirá en demonio para quedar residente en el sistema permanentemente hasta que se termine mediante kill.
Programaremos nuestra aplicación para que informe de los errores en el log, genere un fichero pid, cree un hilo de ejecución que se convierta en demonio y escriba una cadena cada segundo mientras espera a que se termine la aplicación mediante una señal.
Dado que la aplicación no muestra datos al usuario este no sabe que está pasando por dentro, por eso habrá que informarle a través de los log. De esto se encargan las funciones openlog y syslog. Toda la información podrá ser consultada en el fichero /var/log/syslog.
Muchos demonios generan en el directorio /var/run un fichero con el nombre de la aplicación y extensión pid. Dentro guardan el PID o identificador de proceso en formato numérico. Con esto se obtienen dos ventajas: por un lado la misma aplicación sabe si ya hay una copia de ella misma ejecutandose para no duplicar procesos; por otro lado como guarda el pid dentro del fichero cualquier proceso puede leerlo para poder comunicarse con la aplicación (por ejemplo el comando kill que explicaré más adelante).
Para crear un demonio la aplicación principal crea un hilo de ejecución con fork y la aplicación principal se saldrá. Esto provoca que el hilo de ejecución se quede huerfano. Después se ejecuta umask para no heredar los permisos de los ficheros del proceso padre. A continuación se ejecuta setsid para que el proceso huerfano tenga su propio SID y sea un proceso independiente para el sistema operativo. Por otro lado cambiaremos de directorio de ejecución para apuntar a la raiz con un chdir ya que es el único directorio seguro que existe en linux. Finalmente cerraremos con un close la entrada estandar, la salida estandar y la salida de errores ya que no las necesitamos.
Con la función signal haremos que cuando el usuario (o init) nos envíe un comando kill (sin el -9) salgamos de la aplicación limpiamente. Esto es debido a que cuando se ejecuta kill <pid> lo que se está haciendo realmente es enviar una señal SIGTERM a la aplicación. La aplicación recupera la señal y ejecuta la función asociada mediante la función signal. En el ejemplo de más abajo veremos que se asocia a la función adios que simplemente cambia una variable para que se salga del bucle principal. Mientra se espera a recibir la señal, cada segundo se está escribiendo en el fichero /tmp/nohacenada.txt una cadena.
Al salir del bucle se escribirá en el fichero anterior otra cadena y cerramos ese fichero. Igualmente cerraremos el log y se borrará el fichero pid para que se pueda volver a ejecutar la aplicación nuevamente (en un arranque del sistema o nosotros mismos).
Dejo el código fuente de la aplicación llamado nohacenada.c para que os hagais una idea y poder hacer vuestros demonios basados en este:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>
#include <signal.h>
#define FICHERO_PID "/var/run/nohacenada.pid"
volatilecharestado=0;
/* Función llamada cuando se recibe una señal SIGTERM */
voidadios(intsignum)
{
estado=1;
}
intmain(void)
{
structstat st;
FILE*fichero_pid;
FILE*fichero_prueba;
pid_t pid;
pid_t sid;
/* Activamos el manejo de mensajes al demonio de syslogd */
syslog(LOG_ERR,"Ya existe un proceso similar cargado");
exit(EXIT_FAILURE);
}
/* Creamos el fichero PID */
fichero_pid=fopen(FICHERO_PID,"w");
if(fichero_pid==NULL)
{
syslog(LOG_ERR,"No se pudo crear el fichero PID");
exit(EXIT_FAILURE);
}
/* Creamos un hilo de ejecucion */
pid=fork();
/* Si no se pudo crear el hilo */
if(pid<0)
{
syslog(LOG_ERR,"No se pudo crear el proceso hijo");
fclose(fichero_pid);
exit(EXIT_FAILURE);
}
/* Si se pudo crear el hilo */
elseif(pid>0)
{
/* Escribimos en el fichero PID el identificador del proceso */
fprintf(fichero_pid,"%d\n",pid);
fclose(fichero_pid);
exit(EXIT_SUCCESS);
}
/* Se evita el heredar la máscara de ficheros */
umask(0);
/* Convertimos el proceso hijo a demonio */
sid=setsid();
/* Si no se pudo convertir en demonio */
if(sid<0)
{
syslog(LOG_ERR,"No se pudo crear el demonio");
unlink(FICHERO_PID);
exit(EXIT_FAILURE);
}
/* Cambiamos al directorio raiz */
if(chdir("/")<0)
{
syslog(LOG_ERR,"No se pudo cambiar el directorio");
unlink(FICHERO_PID);
exit(EXIT_FAILURE);
}
/* Cerramos los descriptores de archivo que no usaremos */
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
/* Abrimos el fichero de prueba */
fichero_prueba=fopen("/tmp/nohacenada.txt","w");
if(fichero_prueba==NULL)
{
syslog(LOG_ERR,"No se pudo crear el fichero de prueba");
unlink(FICHERO_PID);
exit(EXIT_FAILURE);
}
/* Registramos la función que recibe la señal SIGTERM */
signal(SIGTERM,adios);
/* Bucle principal que se ejecuta hasta que se reciba un SIGTERM (ejecución del comando kill) */
while(estado==0)
{
/* Escribimos la cadena, nos aseguramos que se vuelque al disco y esperamos un segundo */
fprintf(fichero_prueba,"prueba\n");
fflush(fichero_prueba);
sleep(1);
}
fprintf(fichero_prueba,"me voy\n");
/* Cerramos el fichero de prueba */
fclose(fichero_prueba);
/* Borramos el fichero PID */
unlink(FICHERO_PID);
/* Cerramos el log */
closelog();
}
Para compilarlo depende del toolchain para cada plataforma, pero sería algo parecido a esto:
Shell
1
gcc-onohacenada nohacenada.c
Como seguramente hayais instalado un compilador cruzado en vuestro ordenador de sobremesa, habrá que subir el fichero ejecutable a la placa. Yo recomiendo que useis scp en linux o winscp en windows si la placa tiene acceso a la red ethernet o wifi ya que hace uso del demonio sshd (el que se usa para las sesiones de consola remotas por ssh).
Una vez subido el fichero a la placa debemos darle permisos de ejecución, asignarle como propietario al usuario root y moverlo a la ruta /usr/sbin a través de la consola shell con el usuario root de la siguiente manera:
Shell
1
2
3
chmod775nohacenada
chownroot:root nohacenada
mvnohacenada/usr/sbin
Ahora podeis probar si funciona realmente simplemente ejecutando:
Shell
1
/etc/init.d/nohacenada start
Podeis comprobar en la carpeta /tmp si hay un fichero llamado nohacenada.txt y consultarlo, también podeis mirar en la carpeta /var/run si existe un fichero llamado nohacenada.pid y visualizarlo. Si no veis nada podeis mirar el fichero /var/log/syslog para ver si hubiese algún error de la aplicación nohacenada.
Y para pararlo:
Shell
1
/etc/init.d/nohacenada stop
Con esto espero que le saqueis bastante provecho a vuestras placas con linux embebido ya que así conseguis que nada más encenderla se ejecute el programa sin tener que hacer vosotros nada por vuestra cuenta. Quizá en el futuro explique cómo programar módulos para el kernel y extender la funcionalidad de vuestra placa aún más lejos (drivers para hardware, sistemas de ficheros, llamadas al sistema, etc).
Muchas veces he querido usar un transistor NPN para actuar como interruptor y poder des/activar otras partes del circuito que con las patas de un microcontrolador no se puede hacer directamente (un relé, una bombilla de 12 voltios, otro circuito, etc).
Un transistor puede ser activado (saturación) o desactivado (corte) desde un microcontrolador, pero es necesario poner una resistencia entre la pata del micro y la base del transistor. En este artículo explicaré como se puede calcular de una forma sencilla.
Dependiendo de la carga que queramos manejar debemos seleccionar un transistor NPN u otro. No es lo mismo usar un BC107 que permite tensiones de hasta 45 V. y corrientes de hasta 100 mA. que un 2N3055 que permite tensiones de hasta 60 V. y corrientes de hasta 15 A. Aquí podemos ver unos cuantos para ver cuál se adapta mejor a nuestras necesidades. Por eso debemos saber qué corriente pasa por el punto donde queremos poner el transistor para que actúe como interruptor.
Una vez que hemos seleccionado el transistor , debemos calcular qué resistencia debemos poner entre la patilla del microcontrolador que elijamos y la patilla base del transistor. Para eso primero debemos saber qué hFe (ganancia de corriente) mínima tiene nuestro transistor y nada mejor que consultar el datasheet para saber ese dato.
Después con la siguiente fórmula ya podemos calcular qué resistencia necesitamos:
Voltaje: Es la tensión que proporciona la pata del microcontrolador, normalmente 5 o 3,3 V. Se resta 0,7 V. porque es la caída de tensión típica entre la base y el emisor de un transistor, aunque lo puedes mirar en el datasheet del transistor como Vbe.
Corriente: Es la corriente que consume el circuito que queremos encender o apagar.
hFe: Es la ganancia de corriente (current gain) que tiene el transistor (si hay varios valores elegir el más pequeño).
El resultado es el valor en ohmnios de la resistencia que necesitamos poner.
Así por ejemplo vamos a calcular la resistencia que usaremos para manejar un circuito que consume 500 mA. funcionando a 12 V. y gestionado por un microcontrolador PIC cuya patilla da una tensión de 5 voltios. Lo primero es buscar un transistor que se adapte a nuestras necesidades, mirando la lista anterior el 2N3053 nos viene bien ya que permite tensiones de hasta 40V. y corrientes de hasta 700 mA. Podemos ver que su hFe es de 50, por tanto la fórmula aplicada sería:
Una resistencia de 430 ohm. no es una resistencia común, por lo que buscaremos el valor más aproximado de las resistencias comerciales, que en este caso podríamos elegir entre una de 390 ohm. o una de 470 ohm.
Hace tiempo que no escribo en el blog y esto es debido al nacimiento de mi hija. Pero tengo pendientes varios proyectos que iré publicando a lo largo del año. Ahora me he dado de alta en twitter como @sistemasorp donde escribiré también mis ideas, acciones, inquietudes, etc en 140 caracteres.
A finales de 2009 los creadores de Processing anunciaron que iban a dar soporte a la plataforma de Android. La verdad es que fue una noticia muy buena, ya que el famoso entorno de creación de gráficos animados iba a poder ejecutar sketchs dentro de móviles que tuviesen a Android como sistema operativo. Pero no se han quedado ahí y además admite la posibilidad de usar las características embebidas en los dispositivos móviles como el GPS, la brújula electrónica, enviar y recibir sms, etc y por supuesto bluetooth.
Lo que explicaré en este artículo son los pasos que he seguido para crear una conexión desde mi teléfono Android con un dispositivo bluetooth y poder enviarle información a la par que recibirla dibujando los resultados de una forma gráfica con Processing para Android.
Ya en su momento me dediqué a programar en symbian algo parecido:
Sin embargo finalmente lo deseché porque la plataforma de symbian tiene ya poco futuro y porque las APIS eran confusas, mal explicadas y no se puede acceder a todos los elementos de hardware con ellas.
Por otra parte no hay que compararlo con Amarino, ya que Amarino es una aplicación ya hecha para Android con una interfaz definida y está pensado para usarse con Arduino, sin embargo aquí vamos a programar nuestra propia aplicación con su propia interfaz para cualquier dispositivo que tenga un controlador bluetooth.
Dicho esto lo primero es necesario instalar todo el entorno de desarrollo, aquí viene bien explicado en castellano por lo que no considero necesario añadir más.
Antes de empezar, sería interesante que echarais un vistazo a dos páginas para entender mejor lo que viene a continuación. Por una parte la API de Android para bluetooth para comprender qué posibilidades nos ofrece. Por otra parte unos conceptos y un ejemplo de cómo usar Android y gestionar el hardware con Processing. A los que hayáis programado en Processing puede resultar extraño que además de poder usar las funciones básicas del entorno, se puedan incluir librerias y código de Java y la API Android. Esto es debido a que tanto Android como Processing tienen una fuerte vinculación con el lenguaje de programación Java y su unión ha creado una suerte de mescolanza que reúne lo mejor de ambos mundos.
El objetivo es que podamos crear una interfaz completa para que nos muestre los dispositivos bluetooth que hay alrededor, a continuación podamos elegir uno de ellos y finalmente conectarnos a este para enviar y recibir datos. En este caso en concreto voy a conectar mi móvil HTC Wildfire S con una placa Arduino a través de un módulo bluetooth. La placa Arduino enviará a mi móvil cada segundo un valor comprendido entre 0 y 39 y este lo mostrará en su pantalla. En el móvil habrá dibujado un botón que cuando se pulse enviará un 0 a la placa Arduino para encender o apagar el led del pin 13.
Este es el código fuente del sketch para la placa Arduino:
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
booleanestado;
voidsetup()
{
Serial.begin(9600);
pinMode(13,OUTPUT);
digitalWrite(13,LOW);
randomSeed(analogRead(0));
estado=false;
}
voidloop()
{
delay(1000);
Serial.write(random(40));
while(Serial.available()>0)
{
Serial.read();
estado=!estado;
digitalWrite(13,estado);
}
}
Ahora la parte de Android y Processing. A partir de aquí iré explicando los conceptos a medida que publico el código fuente para comentar los aspectos que considero que son más relevantes y finalmente pondré el código fuente integro.
Para poder programar el bluetooth de nuestro dispositivo basado en Android junto con Processing empezaremos por crear un sketch, daremos los permisos BLUETOOTH y BLUETOOTH_ADMIN en el menú Android/Sketch Permissions e importaremos las clases necesarias para que todo funcione:
Java
1
2
3
4
5
6
7
8
9
10
11
12
importandroid.bluetooth.BluetoothAdapter;
importandroid.bluetooth.BluetoothDevice;
importandroid.bluetooth.BluetoothSocket;
importandroid.content.BroadcastReceiver;
importandroid.content.Context;
importandroid.content.Intent;
importandroid.content.IntentFilter;
importjava.util.ArrayList;
importjava.io.IOException;
importjava.io.InputStream;
importjava.io.OutputStream;
importjava.lang.reflect.Method;
Hay algunas librerías que, como veremos más adelante, podemos prescindir de ellas dependiendo de lo que queremos hacer y lo bien o mal que nos funcione.
A continuación vamos a añadir los métodos típicos de un sketch de Processing: setup y draw:
En el método setup forzamos a que la resolución de la pantalla sea de 320 píxeles de ancho x 480 píxeles de alto (HVGA), ya que es una configuración muy extendida dentro del mundo de los móviles. Establecemos que la frecuencia de refresco sea de 25 fps. Creamos dos tipos de fuentes con las que dibujar después los textos que queramos.
En el método draw vamos a simular varias ventanas dependiendo del estado en el que se encuentre nuestra aplicación en cada momento. Así nada más empezar se muestra la ventana de búsqueda de dispositivos, a continuación se ofrece al usuario que seleccione uno de los dispositivos mostrados:
Después se intenta conectar con el dispositivo elegido y finalmente si tiene éxito se muestra la ventana con los datos enviados por Arduino y con el botón que mencionaba antes:
Si algo falla se muestra el error en una ventana:
Un sketch de Proccesing para Arduino se trata como una Activity, por eso se puede inicializar todo en el evento OnStart cuando es llamado al arrancar la aplicación (OnCreate no es llamado).
En el evento OnStart primero debemos llamar al método onStart de la clase padre para inicializar correctamente. Después recuperamos el adaptador bluetooth del móvil y comprobamos si está activo. Si está activo empezamos a mostrar la lista de dispositivos. Si no lo está, lanzamos una petición para activarlo. En esta petición Android nos mostrará un mensaje similar a este en el móvil para activar el bluetooth:
Pulsemos la opción que pulsemos, se llamará al evento onActivityResult:
Si hemos pulsado Si entonces empezaremos a mostrar la lista de dispositivos, si hemos pulsado No iremos directamente a la pantalla de error.
Cuando se abandona la aplicación se llama al evento onStop (onDestroy no es llamado) y aquí se pueden liberar los recursos que hayamos utilizado antes de terminar definitivamente.
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
voidonStop()
{
println("onStop");
/*
if(registrado)
{
unregisterReceiver(receptor);
}
*/
if(socket!=null)
{
try
{
socket.close();
}
catch(IOException ex)
{
println(ex);
}
}
super.onStop();
}
En el evento OnStop vamos a hacer dos cosas, aunque una de ellas está comentada por una razón que explicaré más adelante. Lo que se ve es que se cierra un socket que no es otra cosa que la conexión que hayamos establecido con nuestro dispositivo para poder liberar los recursos asociados a este. Finalmente se llama al método onStop de la clase padre para cerrar correctamente la aplicación.
El método empieza sirve para rellenar la lista de dispositivos bluetooth que más tarde mostraremos al usuario.
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
voidempieza()
{
dispositivos=newArrayList();
/*
registerReceiver(receptor, new IntentFilter(BluetoothDevice.ACTION_FOUND));
registerReceiver(receptor, new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_STARTED));
registerReceiver(receptor, new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED));
Inicializamos un array que rellenaremos de objetos BluetoothDevice. Aquí comento una problemática que me he encontrado: Puedes incluir los dispositivos que hay alrededor del móvil o bien incluir los que previamente se hayan emparejado. La parte que está comentada (y por tanto la del método onStop que desregistra el Receiver) se usa para lanzar un proceso de búsqueda de dispositivos bluetooth que estén cerca de nuestro móvil: se registra el empiece, cada dispositivo que se encuentre y el final de la búsqueda. Para manejar estos eventos se usa una clase del tipo BroadcastReceiver ya inicializada:
Cuando se empieza la búsqueda se encuentra en el estado 0 para mostrar la lista de dispositivos a medida que van apareciendo. Por cada dispositivo bluetooth que se encuentre se añade al array de dispositivos bluetooth, Cuando termina la búsqueda se pasa al estado 1 para que el usuario seleccione el dispositivo al que quiere conectarse.
El hacerlo de esta forma provoca que más adelante cuando se conecte al dispositivo bluetooth se tenga que mostrar una ventana preguntando por un código de emparejamiento para vincular el móvil con este.El problema es que esa ventana no se visualiza cuando están los gráficos mostrándose y el proceso de conexión dará finalmente un error. Por eso la otra alternativa que he encontrado es emparejar el dispositivo bluetooth a mano con el móvil y rellenar la lista de dispositivos con los que ya están vinculados en el móvil, así no pedirá el código de emparejamiento y se conectará con el dispositivo bluetooth directamente.
En varias ocasiones hay que capturar donde pulsa el dedo del usuario para saber qué acción quiere realizar. Esto se consigue con el evento mouseReleased:
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
voidmouseReleased()
{
switch(estado)
{
case0:
/*
if(registrado)
{
adaptador.cancelDiscovery();
}
*/
break;
case1:
compruebaEleccion();
break;
case3:
compruebaBoton();
break;
}
}
Depende del estado en el que encontremos hará una acción diferente. Cuando estamos en el estado 0 y si se usara la búsqueda de dispositivos bluetooth (pero no es así por los motivos que he comentado) al pulsar en la pantalla cancelaría la búsqueda y lanzaría un ACTION_DISCOVERY_FINISHED al BroadCastReceiver visto anteriormente. Si estamos en el estado 1 se buscaría qué dispositivo bluetooth de la lista ha pulsado el usuario para conectarse a el. Si estamos en el estado 3 se comprueba si se ha pulsado el botón que envía un byte al dispositivo bluetooth.
El método que comprueba que elección ha hecho el usuario en el estado 1 es compruebaEleccion:
Con una sencilla fórmula matemática se puede sacar el dispositivo bluetooth que el usuario ha elegido en base a en qué parte de la pantalla ha pulsado. Después se pasa al estado 2 (Conexión al dispositivo bluetooth).
La lista de dispostivos bluetooth se hace con el método listaDispositivos:
Se recorre el array de dispositivos bluetooth mostrando su nombre y su dirección MAC. Es usada tanto en el estado 0 (listado de dispositivos bluetooth a medida que van apareciendo en la búsqueda) como en el estado 1 (selección del dispositivo bluetooth al que conectarse).
La conexión con el dispositivo bluetooth se hace con el método conectaDispositivo:
Aquí ocurre un caso especial. Para conectar con el dispositivo bluetooth se debe crear un socket con el dispositivo y después hacer la conexión propiamente dicha con el método connect. Sin embargo dependiendo de con qué dispositivo se conecte puede dar problemas el método que se use para obtener ese socket. La versión normal es la que está en el metodo y se supone que es la estándar, pero a mi me dio problemas al conectar al blueooth de un PC y tuve que usar las dos líneas que aparecen comentadas . Sin embargo con el módulo bluetooth de Sure la línea que aparece actualmente ha funcionado sin problemas. Como estamos creando una conexión SPP (como si fuese un puerto serie virtual) a través del protocolo RFCOMM, debemos obtener el socket que conecta con el servicio 00001101-0000-1000-8000-00805F9B34FB, que es el identificador único de servicio para SPP en el estandar Bluetooth. El método connect es bloqueante por lo que dejará la aplicación paralizada hasta que se conecte realmente o surja un error. Google dice que se debe usar un thread para separar el hilo de ejecución principal con el de la conexión, pero creo que está solución es más sencilla. Una vez se ha conectado se pueden recuperar el stream de entrada y el de salida para recibir y enviar datos respectivamente con el dispositivo bluetooth. A continuación se pasa al estado 3 (Visualización de datos e interacción con el botón). Si hubiese algún error se pasaría al estado 4 mostrando qué lo ha provocado.
El método que muestra los datos enviados desde el dispositivo bluetooth es muestraDatos:
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
voidmuestraDatos()
{
try
{
while(ins.available()>0)
{
valor=(byte)ins.read();
}
}
catch(Exception ex)
{
estado=4;
error=ex.toString();
println(error);
}
background(0);
fill(255);
text(valor,width/2,height/2);
stroke(255,255,0);
fill(255,0,0);
rect(120,400,80,40);
fill(255,255,0);
text("Botón",135,425);
}
Simplemente comprueba si se ha recibido algún byte y lo muestra. También muestra el botón ficticio que puede pulsar el usuario.
El método que se ejecuta cuando el usuario pulsa el botón es compruebaBoton:
Si se comprueba que el usuario ha pulsado en el área del botón se envía un byte con valor 0 al dispositivo bluetooth, que actualmente lo que hace es encender o apagar el led de la placa Arduino. Si hubiese algún error se pasaría al estado 4 mostrando qué lo ha provocado.
Finalmente el método que muestra los errores es muestraError:
Java
1
2
3
4
5
6
7
8
9
10
voidmuestraError()
{
background(255,0,0);
fill(255,255,0);
textFont(f2);
textAlign(CENTER);
translate(width/2,height/2);
rotate(3*PI/2);
text(error,0,0);
}
Simplemente muestra el texto del error de forma apaisada para mostrar cadenas largas.
Usamos cookies para asegurar que te damos la mejor experiencia en nuestra web. Si continúas usando este sitio, asumiremos que estás de acuerdo con ello.Aceptar