Ubuntu: Conectarse a redes inalámbricas sin ser administrador

Se me ha dado la siguiente situación:

Mi hermana tiene una cuenta de usuario en Ubuntu en su portátil. Cuando sale de casa con el portátil no puede conectarse a ninguna red inalámbrica nueva, ya que aparece un mensaje que indica que:

La política del sistema evita la modificación de la configuración de la red para todos los usuarios

La ventana que saca este mensaje solicita al mismo tiempo la contraseña del usuario administrador de la máquina, y eso es un problema para esta situación particular.

La solución es editar el archivo:

/usr/share/polkit-1/actions/org.freedesktop.NetworkManager.policy

Y en la sección action con id igual a org.freedesktop.NetworkManager.settings.modify.system, buscar la sección allow_active.Habrá que cambiar su valor auth_admin_keep por yes.

Nota 1: Para más información y explicación consultar la documentación de PolicyKit.

Nota 2: En un sistema multiusuario donde no tengas control sobre los usuarios esto puede suponer un serio problema. Un usuario malicioso podría dejar sin conexión a todos los demás. En mi caso particular no supone un problema, ya que es un portátil que va a usar una única persona. El uso de este permiso servirá para poder conectarse a cualquier red inalámbrica sin permiso explícito del administrador.

Etiquetas: , , , :: Archivado en GNU/Linux, Sistemas Operativos

Copia de archivos de más de 2GB por red

Me he encontrado ante el problema de tener que copiar un archivo de 5GB desde un ordenador con Windows 7 hacia un iMac con MacOS X 10.7.3. Ambos sistemas toleran archivos grandes, de más de 2GB, pero las utilidades de compartir en red tienen problemas con estos archivos.

Indagando un poco en la red he encontrado un modo de hacer la copia más rápida posible, y se trata del uso de netcat.

En el equipo origen hay que hacer:

cat [bigfile] | nc -l -p [TCPPort]

Por ejemplo: cat filename | nc -l -p 3333

Y en el destino:

nc [srcIP] [TCPPort] > [bigfile]

Por ejemplo: nc 192.168.27.2 3333 > filename

El proceso, al menos desde Netcat en Cygwin en Windows 7 de 64 bits no termina por sí mismo, de modo que es conveniente una vez que veamos el fichero completamente copiado cortar con Ctrl+C.

También es interesante el uso de MD5SUM en origen y destino para comprobar que todo ha ido correctamente.

Etiquetas: , :: Archivado en Sistemas Operativos, Software

Fuse layer for ADB – adbfuse

En los últimos días he estado trabajando en un proyecto que permite mostrar el contenido de un móvil con Android como si fuese una unidad de disco externa.

Las ventajas que tiene respecto a activar la conexión por USB que trae Android de serie son varias, entre ellas:

  • Acceso completo al sistema de ficheros del teléfono. La tarjeta de memoria interna es accesible en /mnt/sdcard.
  • Acceso simultáneo desde el teléfono y desde el ordenador. De este modo no dejarán de funcionar las aplicaciones que han sido pasadas a la tarjeta de memoria mientras la usamos desde el ordenador.

Se trata de una primera versión y en directorios donde hay muchos archivos, por ejemplo la carpeta de fotos o de música, el acceso puede ser algo lento. Las siguientes versiones de adbfuse irán mejorando este y otros aspectos.

Se trata de algo para usuarios avanzados, ya que el teléfono ha de estar rooteado, tener instalado BusyBox, y saber montar un sistema de archivos FUSE en Linux.

Se puede descargar en la web del proyecto tanto la versión empaquetada como el código fuente con Mercurial. El proyecto ha sido liberado bajo la licencia GNU GPLv3.

Etiquetas: , , :: Archivado en Android, Programación, Python

MetaWatch Firmware

Este verano me compré un reloj MetaWatch. Este reloj nace según sus creadores como una plataforma para desarrollo de manera que está documentado desde antes de venderse. La documentación la actualizan, el firmware lo actualizan y la aplicación para Android la actualizan también.

Además proporcionan el código fuente tanto de la aplicación para Android como del Firmware del mismo. Se me ocurrió hacerle una modificación, y es que cuando se pulsa el primer botón, el de arriba a la derecha, aparece mi tarjeta de visita. En este artículo explicaré de forma resumida el proceso para conseguir mi objetivo.

Lo primero es generar un qrcode en la web de zxing. Se rellena la información pertinente y se genera de tamaño S para que quepa en la pantalla de 96×96 del reloj. Aunque la imagen generada es un poco más grande aprovecharemos el hecho de que tiene un marco blanco alrededor despreciable sin pérdida de información. Una vez generado se abre con Gimp y se cambia el tamaño del lienzo a 96×96 recortando la zona blanca del alrededor tanto como sea necesario para que entre, sin redimensionar la imagen. Se guarda como BMP indexado de dos colores sin comprimir y ya tenemos la imagen preparada.

En el código fuente del firmware del reloj, las imágenes se encuentran directamente en el formato aceptado por el búfer de la pantalla, es decir, como array de bytes. Al tener la pantalla 96×96 píxeles como ya decíamos antes, se tiene un array de 96 filas con 12 bytes por fila. Doce bytes por ocho bits por byte hacen un total de 96 columnas.

Para convertir cualquier imagen BMP indexada con dos colores a array de bytes he realizado este pequeño script en python usando parte del código de PyMetaWatch:

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
#!/usr/bin/env python
import sys
import Image
 
def main():
  image = Image.open(sys.argv[1])
  pix = image.load()
 
  for y in range(0, 96):
    rowdat = ""
    for x in range(0,96,8):
      byte=0
      for pindex in range(0,8):
        pixel=pix[x+pindex,y]
        if (pixel > 0):
          pixel = 1
 
        byte=((byte>>1)|(pixel<<7))
      rowdat="%s%s" % (rowdat,chr(byte))
 
    myrow = ""
    for dat in rowdat:
      myrow += "0x%02x," % ord(dat)
 
    print myrow
 
if __name__ == '__main__':
  main()

Pasando como parámetro el nombre del archivo BMP devuelve un bloque de caracteres que habrá que usar en el código fuente del firmware. Lo que hice fue volcar la salida a un fichero y después copiar y pegar el resultado. Con eso es suficiente para nuestro objetivo final, no buscábamos hacer un programa de conversión sino que tan solo ha sido una herramienta intermedia.

Finalmente hay que buscar en el fichero LcdDisplay.c la definición de pBarCodeImage y sustituir su valor por el generado por el script anterior. Se compila el proyecto y se programa el nuevo firmware al reloj siguiendo las mismas instruciones que existen para la actualización oficial. Y ya está.

Gracias a draxus por encontrar la ubicación del QRCode original en el código fuente del firmware.

Etiquetas: , , , :: Archivado en hardware, Python

Threads y animaciones en Android

En la entrada anterior vimos cómo convertir un gif animado en una animación para Android. Lo siguiente que queremos hacer es usar esa animación en una aplicación.

Es un problema conocido que si se llama al método AnimationDrawable.start() desde métodos del Activity como en Activity.onCreate(), la animación no se moverá, y solo se verá la primera imagen de la misma. No habrá ningún error, no habrá ningún problema, y sin embargo la animación no se moverá.

La solución la aporta un usuario en la misma hebra del problema, y viene a decir que hay que crear una clase Starter que implemente la interfaz Runnable y crear un objeto de este tipo dentro del método ImageView.post(). De este modo el método run() de la clase Starter se ejecutará en la misma hebra que el método del Activity (la hebra de interfaz del usuario – UI Thread) y además después de que nuestro ImageView haya sido correctamente inicializado y mostrado en pantalla.

Esta primera parte iría en el método del Activity donde queremos asignar e iniciar la animación.

1
2
3
4
5
// --- %< ---
imageView.setBackgroundResource(R.drawable.throbber);
AnimationDrawable d = (AnimationDrawable)imageView.getBackground();
imageView.post(new Starter(d));
// --- %< ---

Y por otro lado tendríamos que crear una clase Starter() como la siguiente.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Starts the animation drawable passed to the constructor.
*/
class Starter implements Runnable {
    private AnimationDrawable animationDrawable = null;
 
    public Starter(AnimationDrawable animationDrawable) {
        this.animationDrawable = animationDrawable;
    }
 
    public void run() {
        this.animationDrawable.start();
    }
}

Etiquetas: , , :: Archivado en Android

Uso de gif animado en Android

En Android se suele usar un ImageView para dibujar una imagen. La imagen puede provenir de un recurso interno, de una imagen externa, o de un objeto drawable que se le pase directamente al método .setImageDrawable().

En cualquier caso, en la mayoría de dispositivos no será posible mostrar en este tipo de contenedor un gif animado, ya que según la documentación tan solo se representará el primer frame de la imagen animada.

El modo de representar correctamente una animación en android es usando un recurso creado específicamente para ello. De los dos tipos de animaciones que se pueden crear en Android la que nos interesa en esta ocasión es la que se conoce como Frame Animation. La idea es sencilla y es muy similar a la de los GIF animados: un archivo xml define qué imágenes se muestran y durante cuánto tiempo, las imágenes deben de encontrarse accesibles como recursos internos de la aplicación. Para más información véase la documentación sobre Frame Animation.

La cuestión es que hay un montón de GIFs animados que podemos querer incluir en nuestra aplicación para Android, así que es necesario hacer una conversión de un formato a otro de un modo más o menos automatizado.

El siguiente guión en python extrae los frames completos así como la información temporal y espacial de cada uno de ellos regenerando imágenes en caso de tratarse de un GIF optimizado, para formar un recurso de imagen completo para Android. La animación así creada funcionará correctamente en cualquier dispositivo en cualquier versión de Android. Para más información véase la clase AnimationDrawable.

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
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
 
import sys, os
import subprocess
 
# When no delay is specified in a gif file, 100ms is default
DEFAULT_DELAY_MS = 100
 
input_file = None
output_file = None
 
try:
    input_file = sys.argv[1]
    output_file = "%s.xml" % (input_file.split(".")[0],)
except:
    print "Usage: %s <input.gif>" % (sys.argv[0],)
    sys.exit(1)
 
 
class Anim(object):
    def __init__(self):
        self.content = []
 
    def add_header(self):
        self.content.append('<?xml version="1.0" encoding="utf-8"?>\n')
        self.content.append('<animation-list\n')
        self.content.append('    xmlns:android="http://schemas.android.com/apk/res/android"\n')
        self.content.append('    android:oneshot="false">\n')
 
    def add_item(self, resource, duration):
        self.content.append(
            '    <item android:drawable="@drawable/%s" android:duration="%d" />\n' %
            (resource, duration))
 
    def add_footer(self):
        self.content.append('</animation-list>\n')
 
    def write_file(self):
        file_descriptor = open(output_file, "w")
        file_descriptor.writelines(self.content)
        file_descriptor.close()
 
 
class Gif(object):
    def __init__(self, gif):
        self.input_file = gif
        gif_info = subprocess.Popen(
            ["gifsicle", "--info", input_file],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE)
 
        (gif_info_out, gif_info_error) = gif_info.communicate()
 
        self.gif_info_lines = gif_info_out.splitlines()
 
    def get_info(self):
        return self.gif_info_lines
 
    def explode(self):    
        return_code = subprocess.call(
            ["gifsicle", "--explode", "--unoptimize", self.input_file])
 
        return not return_code
 
    def to_png(self, gif_frame):
        return_code = subprocess.call(["gif2png", "-d", "-s", "-O", gif_frame])
 
        return return_code
 
 
def main():
    print "Processing %s..." % (input_file,)
 
    anim = Anim()
    anim.add_header() 
 
    gif = Gif(input_file)
 
    if not gif.explode():
        print "Error exploding gif input file %s" % (input_file,)
        #sys.exit(1)
 
    info_lines = gif.get_info()
 
    counter = 0
    for line in info_lines:
        line = line.strip()
 
        if line.startswith("disposal"):
            line_items = line.split()
 
            if len(line_items) == 4:
                milliseconds = int(round(float(line_items[3][:-1]) * 1000))
            else:
                milliseconds = DEFAULT_DELAY_MS
 
            # Rename and convert gif to optimized png
            renamed_gif = "%s_%03d.gif" % (input_file.split(".")[0], counter)
            os.rename("%s.%03d" % (input_file, counter), renamed_gif)
            result = gif.to_png(renamed_gif)
 
            anim.add_item(renamed_gif.split(".")[0], milliseconds)
            counter = counter + 1
 
    anim.add_footer()
    anim.write_file()
 
    os.unlink(input_file)
    sys.exit(0)
 
if __name__ == "__main__":
    main()

Es necesario tener instalado gifsicle, y para la optimización en tamaño y conversión a png de las imágenes resultantes es necesario tener también instalado, y en el $PATH, gif2png.

Descargar getanim.py.

Etiquetas: , , , , :: Archivado en Android

Bitmap desde NV21 en Android

En el nuevo proyecto que estoy escribiendo para Android, me he visto en la necesidad de analizar cada una de las imágenes que recoge la cámara durante la vista previa de la misma. Según la documentación el formato que deben de implementar los dispositivos con Android es YCbCr_420_SP codificado como NV21. Aunque en principio se podrían especificar otros formatos es extraño que algún terminal tenga otro formato implementado así que deberemos de usar ese si queremos que funcione en el mayor número de plataformas y dispositivos posibles.

Cylon detector pre-early release

Desarrollo de Cylon Detector para Android

En cada frame recibido se desea analizar si existe la cara de una persona, por lo que hay que convertir la imagen a Bitmap y pasarla al detector de caras que provee el API. Pues bien, existe un problema y es que la factoría para crear Bitmaps no acepta ese formato. Hay varias soluciones por la red, pero unas son para Android 2.2 en adelante, otras son implementaciones en C para hacerlas usando el NDK como librería independiente, y otras símplemente son demasiado complejas para lo que aquí se necesita.

Al final lo que he hecho es analizar el formato de los datos en base a su definición y crear el Bitmap a partir de estos datos para la imagenen blanco y negro. Podría hacerse para la imagen en color símplemente leyendo todos los datos y haciendo uso de las funciones de conversión de YCrCb a RGB pero ello requeriría mayor tiempo de proceso incluyendo varias multiplicaciones en coma flotante. Aquí solo necesitamos en principio detectar si hay una cara en la imagen o no, y para ello es suficiente con que la imagen sea en blanco y negro. Para obtener la imagen en blanco y negro lo que he hecho ha sido leer los valores de luminancia (luminosidad), que según el formato NV21 están en la primera parte del array y convertir cada valor a un punto RGB con el mismo valor. El resultado, aunque no se aprecia demasiado bien en la imagen anterior, no es completamente una imagen en blanco y negro, sino que aparecen zonas saturadas de un color amarillo. La conversión que estoy realizando no es del todo correcta pero es cuestión de convertir y acotar correctamente los valores.

Por si a alguien le es de utilidad, esto es un recorte de la parte más importante que he programado para la conversión de NV21 a Bitmap:

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
/**
 * @author nauj27
 * The Utils class contains utilities for CylonDetector.
 */
public class Utils {
 
	/**
	 * See http://www.fourcc.org/yuv.php#NV21 for more information.
	 * We only read luminance for speed up the whole process. 
	 * All colors of the image are set to the luminance value and
	 * this way we obtains a black and white image for processing.
	 * 
	 * @param data The data array in NV21 (YCbCr_420_SP) format.
	 * @return Black and white bitmap decoded from NV21 input data.
	 */
	public static Bitmap getBitmapFromNV21(byte[] data, int width, int height) {
 
		int pixelsNumber = width * height;
		int[] colors = new int[pixelsNumber];
 
		for (int pixel = 0; pixel < pixelsNumber; pixel++) {
			colors[pixel] = Color.rgb(data[pixel], data[pixel], data[pixel]);
		}
 
		return Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
	}
 
}

Editada: El modo correcto de convertir el valor de luminancia a escala de grises para crear la imagen lo he sacado finalmente del proyecto zxing, y es el que dejo a continuación:

1
2
grey = data[pixel] & 0xff;
colors[pixel] = 0xff000000 | (grey * 0x00010101);

Esto es lo que iría dentro del bucle for para que sea interpretado correctamente por el Bitmap.createBitmap como mapa de color ARGB_8888.

Etiquetas: , , , , , , , :: Archivado en Android, Programación

HTML5 Canvas

Me he decidido a echarle un vistazo al HTML5 y a la representación de una superficie Canvas para dibujar en 2D. Este tipo de superficie permitirá en un futuro que ya está aquí hacer juegos para la web en HTML real que se puedan usar en cualquier dispositivo sin necesidad de complementos de terceros, léase Adobe Flash.

La verdadera utilidad y potencia es poder dibujar en tiempo real sobre la superficie y a provechar todas las características de dibujo que nos proporciona, cosas que no he usado en mi ejercicio particular. Sin embargo me ha servido como introducción al uso de canvas en HTML y aquí queda como ejemplo:

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
<html>
  <head>
    <title>HTML 5 Canvas example</title>
    <script type="text/javascript">
        var x = 50;
        var y = 50;
        var dx = 1;
        var dy = 1;
 
        var myCanvas;
        var myContext;
 
        var pelota;
        var fondo;
 
        var main = function() {
            // Se inicializa el canvas
            myCanvas = document.getElementById("mycanvas");
            myContext = myCanvas.getContext("2d");
 
            // Se cogen los elementos de imagen para pintar
            pelota = document.getElementById("pelota");
            fondo = document.getElementById("fondo");
 
            // Se pinta el fondo y la pelota en el canvas
            myContext.drawImage(fondo, 0, 0, fondo.width, fondo.height);
            myContext.drawImage(pelota, x, y);
 
            window.setInterval("moverPelota()", 10);
        };
 
        var moverPelota = function() {
            if ((x > myCanvas.width - pelota.width) || (x < 0)) {
                dx = -dx;
            }
 
            if ((y > myCanvas.height - pelota.height) || (y < 0)) {
                dy = -dy;
            }
 
            x += dx;
            y += dy;
 
            myContext.drawImage(fondo, 0, 0, fondo.width, fondo.height);
            myContext.drawImage(pelota, x, y);
        };
 
    </script>
    <style type="text/css">
      canvas { border: 2px solid black; }
      canvas#mycanvas { width: 320px; height: 170px; }
      img { display: none; }
    </style>
  </head>
  <body onload="main();">
 
    <canvas id="mycanvas">
        Aquí debería de aparecer un balón rebotando.
    </canvas>
 
    <img id="pelota" src="pelota.png" width="49px" height="49px" />
    <img id="fondo" src="fondo.jpg" width="320px" height="170px" />
 
  </body>
</html>

El resultado puede verse en la web del ejemplo de uso de canvas.

Las imágenes se precargan ocultas (con display: none) porque según la especificación del HTML5 si la imagen no ha sido completamente cargada no se pintará nada. Cargándolas de este modo nos aseguramos de que se han leído completamente antes de su uso en el canvas.

Las pruebas las he realizado sobre Mozilla Firefox 4.0 Beta 7. En cualquier navegador que implemente canvas sobre html5 debería de representarse correctamente. Entre estos navegadores se incluyen Safari, Opera o Chrome. Si eres usuario de Internet Explorer ¿a qué esperas para cambiar a un navegador real?

Etiquetas: , , , :: Archivado en HTML, JavaScript