Un voladizo para la mesa del ordenador

Por mucho espacio que tenga siempre acabo necesitando más, y como la mesa del ordenador se me empezaba a quedar pequeña decidí construir un voladizo para ganar espacio, al tener así un estante superior donde poner la impresora y otros elementos.

Empecé por comprar las piezas. En tiendas como Bricoking o Bricocentro recortan tablero a medida y lo cantean en base a nuestras instrucciones. La lista completa es la que aparece en la siguiente imagen, indicando cuantas de cada una se necesitan y el tamaño en centímetros de cada lado. Aquellos lados que tienen un punto son los que deben ir canteados. Las medidas indicadas están pensadas para una mesa de 138 cm de largo, y para tablero de 2 cm de grosor.

piezas

Las piezas azules son los laterales del voladizo, que sostienen el peso; la pieza amarilla es la parte superior, y las verdes son los listones frontal y trasero, que, además de tapar la zona inferior de la pieza superior, le da más resistencia. Las dos piezas naranjas son los estantes laterales, y la pieza rosa y la gris forman el fondo, donde además se colgará el monitor.

Empezamos por fijar a la parte superior los dos listones, usando cuatro ángulos de 20x20mm. Nótese que los listones son cuatro centímetros más cortos que la parte superior para que encajen con los laterales, que tienen dos centímetros de grosor cada uno:

re_IMG_20140513_191355 re_IMG_20140513_191550

re_IMG_20140513_193423

A continuación añadimos las dos piezas laterales, fijándolas también con ángulos tanto a la parte superior como a los dos listones:

re_IMG_20140513_200141

Y con esto tenemos la primera parte:

re_IMG_20140513_200158

Ahora podemos colocarla encima de la mesa y fijarla con cuatro ángulos para que no se mueva ni se nos caiga nada. Otra opción sería utilizar tacos de madera para que no se vea, pero en mi caso prefiero utilizar algo lo más resistente posible:

re_IMG_20140513_213416

El siguiente paso consiste en fijar los estantes laterales (piezas naranjas). Para ello utilicé el soporte ekby töre de Ikea, ideal para estas cosas: fijé dos en cada tablero por la parte trasera, los fijé a la mesa, y finalmente atornillé cada tablero a su pieza lateral por la zona delantera para que el conjunto aguante más peso (mi intención es poner el ordenador encima de una de ellas).

re_IMG_20150114_171233

La última parte es la zona posterior, formada por las piezas rosa y gris. La rosa es la parte frontal, que mirará hacia nosotros, y la gris es la de refuerzo. El motivo es que estas dos piezas sostendrán nuestro monitor, además de reforzar el voladizo para que no tienda a doblarse, por lo que nunca está de más garantizar que tenga una buena resistencia. Empezamos por colocar una sobre otra y atornillarlas para que queden perfectamente solidarias:

re_IMG_20150113_131736 re_IMG_20150113_132939 re_IMG_20150113_134553

Si nos fijamos, la pieza gris es algo más corta que la rosa. Esto es para que no tropiece con el listón posterior que va en el voladizo.

El siguiente paso consiste en fijar el soporte para el monitor. Yo escogí uno fijo porque ocupa menos espacio (la ventaja de colgar el monitor es que ganamos el espacio que ocupaba el soporte, y nos deja mover el teclado hasta el fondo por si queremos leer o hacer alguna otra cosa). Para ello empezamos por medir a qué altura están los agujeros del soporte VESA de nuestro monitor y, en base a ello, fijar la base:

re_IMG_20150113_134730 re_IMG_20150113_135946 re_IMG_20150113_165440

Ahora hay que añadir dos ángulos en la parte superior para fijar el tablero al voladizo:

re_IMG_20150113_170933

A continuación hay que hacer los agujeros para pasar los cables de alimentación y vídeo. Primero hacemos una plantilla de la disposición concreta de los conectores en nuestro monitor (truco: usar los tornillos del soporte para fijar un folio a los agujeros del monitor, y así tener una guía):

re_IMG_20150113_180735

El siguiente paso consiste en pasar la plantilla al tablero, alineándola con el soporte instalado (¡no olvidar darle la vuelta!) y marcar los puntos en los que deben ir los agujeros:

re_IMG_20150113_181203

Hecho esto utilizamos una corona y un taladro para perforar el tablero en los puntos indicados, y procedemos a montar la pieza en la mesa, fijándola a la parte superior mediante los dos ángulos:

re_IMG_20150114_121636 re_IMG_20150114_122420

Ahora fijamos por detrás la parte inferior del tablero para evitar que se mueva, utilizando un ángulo más largo. En la foto vemos que el ángulo se atornilla también por debajo de la mesa:

re_IMG_20150114_123107

Hecho esto colocamos el monitor en el tablero y conectamos los cables:

re_IMG_20150114_123404

Yo añadí a mayores un tubo fluorescente debajo de la balda para tener luz extra si un día quiero hacer algo en la mesa que no sea trabajar con el ordenador. Este tubo está justo detrás del listón frontal:

re_IMG_20150114_155201

Y con esto ya está completo nuestro maravilloso voladizo:

re_IMG_20150114_124608

Arrancando la Raspberry Pi desde un disco duro externo

Estas navidades me he autorregalado una Raspberry Pi, porque no se puede ser un friki de verdad sin una. Mi primera intención es utilizarla para reemplazar al cacharro con Android del que hablé en entradas anteriores. Las ventajas son dobles: para empezar, consigo un sistema 100% integrado, y no el híbrido frankensteniano que tenía antes; y, por otro, me queda el cacharro Android para experimentar más y perfeccionar la técnica de arranque dual.

Para no complicarme la vida escogí la distribución raspbmc. Esta distro tiene algo de truco a la hora de instalarla. Además, como es normal en la Raspberry, es necesario arrancar siempre desde la tarjeta SD, cosa que a mi no me convence porque no quiero que acabe quemada por excesivas escrituras (a fin de cuentas me gusta cacharrear). Para evitarlo, decidí mover el sistema operativo a un disco duro USB de un terabyte, y así tener también sitio donde almacenar más cosas. El procedimiento a seguir es el siguiente: primero descargamos la imagen y la volcamos a la tarjeta con el comando:

sudo dd if=raspbmc-2014-11-24.img of=/dev/sde bs=2048

Asumiendo, claro está, que nuestra tarjeta SD está en /dev/sde.

Una vez creada la tarjeta, la insertamos en nuestra Raspberry y la encendemos SIN CONECTAR A LA RED LOCAL. Lo primero que hará será redimensionar la partición para que ocupe toda la tarjeta, y luego lanzará el XBMC. Y aquí está el primer problema: si en ese momento está conectada a Internet, se bajará una actualización de éste y, por algún motivo que desconozco, fallará y la imagen quedará inutilizable, teniendo que volcar de nuevo el fichero en la tarjeta. Es necesario dejar que arranque primero y se configure, y sólo entonces conectarlo a Internet y dejar que se actualice.

Otro problema que descubrí es que, por defecto, se configura en 1080p, y mi tele no soporta ese modo. Para resolverlo tuve que conectar la placa al monitor del ordenador y allí configurar la salida a 720p.

Una vez que tenemos la tarjeta lista, vamos a volcar todo al disco duro. Si montamos la tarjeta en nuestro PC veremos que tiene dos particiones: una de 73 MB en formato FAT32, y otra en formato EXT4. La primera partición contiene el kernel y demás ficheros de arranque básico, por lo que esa, de momento, no la tocaremos, pues es necesaria para iniciar el arranque; la segunda es la que contiene el sistema Linux, y será la que vamos a mover al disco USB.

Comenzamos por crear tres particiones en nuestro disco duro: una de 20GB en formato EXT4 para el sistema, otra de 4GB de swap, y una tercera con el resto para datos. A continuación, copiamos todos los ficheros de la partición EXT4 de la tarjeta a la partición de sistema del disco duro, utilizando la opción -a de cp para que, al copiar los ficheros, conserven su tipo, propietario, permisos y demás características (de no hacerlo así, los ficheros de dispositivos se copiarían como un fichero normal con el contenido del dispositivo, en lugar de como ficheros especiales; de igual forma los enlaces simbólicos no se copiarían como tales, y más problemas y diferencias). Además, es fundamental hacerlo como root, pues si no, no podría cambiar el tipo de usuario:

sudo cp -a /media/disco_origen /media/disco_destino

Otra opción es comprimirlo en un fichero tar.gz para poder restaurar cómodamente la partición en cualquier momento, pero eso ya es a gusto del consumidor.

Una vez hecho esto vamos a modificar el arranque en la tarjeta SD para que vaya al sistema situado en el disco duro. Para ello abrimos el fichero cmdline.txt, que contiene la línea de arranque para el núcleo. En ella buscamos el parámetro root=/dev/mmcblk0p2 y lo reemplazamos por root=/dev/sda1 (o la partición del disco en el que vamos a copiar el sistema), y añadimos el parámetro rootdelay=5 para asegurarnos de que reconoce el disco antes de intentar arrancar de él.

Por último, podemos editar el fichero /etc/fstab y añadir las siguientes líneas para que monte automáticamente la partición de swap y de datos donde queramos:

/dev/sda2   none   swap   sw        0   0
/dev/sda3   /datos ext4   defaults  0   1

Y con esto tendremos listo nuestro sistema Raspbmc en disco USB. En una próxima entrada comentaré como monté una sesión chroot, porque la raspbmc está basada en Debian Wheezy y cualquier intento de actualizarla a Jessie acaba con el sistema dañado (incluso probé a utilizar pinning, pero es muy fácil que para instalar algún paquete interesante haya que actualizar la libc6, lo que hace que casque todo).

Experimentando con Autovala

Hace un par de meses lancé una nueva versión de Autovala, la 0.99.9, y otra hace menos, la 0.99.10.

La primera, como novedad, permite escoger si se desea utilizar una versión fija del compilador de vala, o si se prefiere que sea Autovala quien escoja la que se debe utilizar. Antes, la versión del compilador se actualizaba siempre en el fichero .avprj a la versión por defecto instalada en el sistema; ahora esto sólo ocurre si el comando vala_version en dicho fichero tiene un asterisco al principio. Si no, se mantendrá dicho valor siempre, y será el valor mínimo necesario para compilar el proyecto.

Respecto a la versión 0.99.10, incluye una petición de un usuario: soporte de pruebas unitarias, lo que permite simplificar la depuración de los proyectos.

Como de costumbre, el propio proyecto incluye abundante documentación en la carpeta doc, en formato HTML.

A mayores estoy preparando para la versión 0.99.11 la generación automática de ficheros de metadatos para paquetes. En este momento genera casi todo lo necesario para crear paquetes .deb y .rpm, pero en el futuro espero poder añadir soporte para más arquitecturas de paquetes, como emerge. Esta versión en desarrollo está disponible en una rama aparte, y me gustaría que la gente la probase antes de mezclarla con main.

Mapear los botones de un ratón logitech

Desde hace tiempo tengo un ratón Performance MX de Logitech. Estoy muy contento con él desde que lo compré, salvo por el detalle de que usar la rueda como botón central es bastante incómodo, porque es difícil no girarla al pulsarla. Por eso lo primero que hice fue mapear la tecla zoom (que está disponible en el pulgar) como el botón central. Para ello hice este pequeño script bash, que añadí a las aplicaciones al inicio de mi sesión:

#!/bin/bash

xmodmap -e "pointer = 1 13 3 4 5 6 7 8 9 10 11 12 2"

Al principio este script funcionaba perfectamente, hasta que un día, dejó de hacerlo. Lo raro era que si lo ejecutaba a mano desde un terminal, funcionaba perfectamente; sólo fallaba si lo ejecutaba como aplicación al inicio de sesión. Esto me obligaba a ejecutarlo manualmente cada vez que encendía el ordenador, lo que era un peñazo, así que empecé a investigar qué era lo que ocurría, y descubrí que el ratón, como dispositivo de entrada, no es visible por el ordenador hasta que se mueve por primera vez. Eso significa que, cuando se ejecutaba el script al principio de la sesión, como para el ordenador aún no había ningún ratón, no hacía nada; pero cuando lo ejecutaba a mano, como ya lo había movido para lanzar el terminal, éste ya aparecía en la lista de dispositivos, y por eso funcionaba.

La solución final consistió en modificar el script para que, al lanzarse, compruebe regularmente si hay un dispositivo Logitech, y sólo entonces ejecute el comando xmodmap. El script final quedó así:

#!/bin/bash

x=1

while [ $x -ne 0 ]
do
	sleep 1
	xinput --list | grep Logitech
	x=$?
done

xmodmap -e "pointer = 1 13 3 4 5 6 7 8 9 10 11 12 2"

Dado que sabemos que tiene que haber un ratón Logitech (si no, no meteríamos este script), comprobamos una vez por segundo si en la lista de dispositivos de entrada aparece el receptor (esa pausa permite no consumir recursos inútilmente mientras esperamos a que aparezca). En el momento en que se cumpla sabemos que el comando xmodmap va a funcionar, así que lo ejecutamos y salimos del script.

Tablet con systemd

Hace un par de días actualicé el sistema Debian que le había instalado a mi tablet, y me encontré con la desagradable sorpresa de que mi gestor de ventanas y mi driver táctil dejaron de funcionar. El motivo es que, recientemente, Debian se ha cambiado a systemd, por lo que tuve que hacer algunos cambios para adaptarlo.

El primer y más fundamental cambio fue reemplazar los scripts en bash por ficheros de configuración de systemd. Este es el fichero para lanzar el driver táctil:

[Unit]
Description=GSLx680 user-space driver launcher for systemd

[Service]
Type=simple
ExecStart=/bin/gslx680 -new_scroll /dev/i2c-1 /etc/gslx680/firmware.cfg
ExecStop=killall gslx680

[Install]
WantedBy=multi-user.target

Este fichero lanza durante el arranque el driver, y durante el apagado del sistema lo mata.

El fichero para lanzar las X es similar:

[Unit]
Description=Launch X11

[Service]
Type=simple
User=debian
ExecStart=/usr/bin/startx
ExecStop=killall xinit

[Install]
WantedBy=multi-user.target

El único cambio es que especificamos con qué usuario queremos lanzar el comando: en este caso el usuario es debian.

Otro cambio que tuve que hacer fue eliminar, en el fichero .xinitrc, el que se lance ck-launch-session, el gestor de sesiones de ConsoleKit. Este ya no es necesario porque de ello se encarga systemd.

Por último, para apagar el sistema el gestor de ventanas ya no ejecuta halt, sino systemctl poweroff, con lo que ya no es necesario que haya un comando para ello con el bit suid activo.

Un último detalle: tuve que desinstalar el demonio pulseaudio para conseguir que reprodujese vídeos. Todavía no se el motivo de que con él lanzado no funcione (el audio queda bloqueado y tanto mplayer como vlc se quedan congelados esperando a que se libere).

Nueva tablet para cacharrear

Actualizado: he retocado algunos detalles de la línea de configuración que eran incorrectos. Al escribir la entrada corté y pegué una línea que no era.

Un colega me regaló una vieja tablet que no funcionaba para aprovechar piezas, pero como encendía y llegaba a hacer algo, decidí ver si podía resucitarla (sí, la pantalla está rota; afortunadamente sólo es el plástico, el LCD está en perfecto estado).

tablet

El primer intento consistió en encenderla pulsando a la vez el botón de encendido y el de subir volumen para entrar en modo de rescate, a ver si conseguía restaurar el sistema Android. Aparentemente funcionó, pero al intentar arrancar se volvía a quedar a la mitad, por lo que empecé a sospechar que el problema estaba en la memoria flash interna, pero para demostrarlo necesitaba acceder al sistema de alguna manera.

Echando un vistazo por dentro encontré que había cuatro pads en una esquina marcados como gnd, tx, rx y 3,3v, por lo que sospeché que podría tratarse de un puerto serie de depuración. Estos puertos serie son bastante habituales en aparatos de consumo, pero tienen el inconveniente de que trabajan con niveles TTL (0 voltios para un cero, o 5 o 3,3 voltios para un uno) en lugar de las tensiones estándar del RS-232 (3/15 voltios para un cero, -3/-15 voltios para un uno). Existen algunos conversores de serie TTL a USB, pero por diversos motivos preferí hacerme una placa con un MAX 3232 y un conversor RS-232 a USB normal y corriente. El MAX3232 es un chip similar al conocido MAX232, que convierte los niveles entre un puerto serie TTL y uno RS-232, pero con la ventaja de admitir tensiones de alimentación entre 3 y 5,5 voltios (el MAX232 está limitado a 5 voltios). Con este chip pude conectar la tablet a mi PC (pulsa en la imagen para ampliar).

tablet_conversor

Un detalle importante es que fue necesario soldar dos puentes para activar el puerto serie. Están marcados con un círculo rojo en la siguiente fotografía (pulsa en la imagen para ampliar):

 

tablet_serial

Una vez hechos los puentes y lanzado el minicom con el puerto serie configurado a 115.200 8N1, apareció el texto de arranque. Ahí pude ver que, como esperaba, primero cargaba el U-Boot, y éste cargaba el núcleo Linux de Android, siguiendo el proceso habitual. Sin embargo, al llegar a cierto punto empezaron a salir errores de lectura de la flash, tal y como temía. Sin embargo, decidí probar si podía conseguir acceso al U-Boot para cambiar las opciones de arranque, y sí, fué posible: pulsando la tecla Return en el terminal varias veces en el momento de encender la tablet detiene el proceso de arranque y ofrece un prompt en el que se puede jugar con muchas opciones.

Con el comando print eché un vistazo a las variables de entorno, y encontré la que me interesaba: la que contiene el parámetro bootargs. Esta variable define los parámetros de arranque que se le pasan al núcleo al arrancar, tales como la partición con el sistema de ficheros raíz y otras. En el caso de esta tablet, sin embargo, el proceso es ligeramente oscuro, pues lo que se hace es llamar a un script que define en cada momento la variable bootargs en función de lo que sea necesario. En concreto, este es el script tal y como viene definido:

set-rfs-ram-ota=setenv bootargs mem=${memtotal} root=/dev/ram0 rw initrd=${load-addr-initrd},0x${filesize} console=ttyS0,115200n8 init=/init androidboot.serialno=${androidboot.serialno}

Para hacer una primera prueba introduje la tarjeta de memoria de mi otra tablet, la cual ya tiene un sistema Debian completo, junto con mi gestor de ventanas para tablets, y modifiqué la entrada anterior con:

setenv set-rfs-ram-ota setenv bootargs mem=${memtotal} root=/dev/mmcblk0p2 rw rootdelay=1 console=ttyS0,115200n8

El dispositivo /dev/mmcblk0p2 es la partición segunda de la tarjeta microSD, que es donde tengo el sistema Debian. Al principio le añadí un rootdelay=7 para retrasar siete segundos el montaje de la partición raíz, pensando que tardaría un poco en detectar la tarjeta, pero al final no es necesario. En cambio, si se quiere arrancar desde una partición en un disco USB, es muy probable que dicha opción sea necesaria para darle tiempo al núcleo a detectar los dispositivos.

Y con  esto la tablet arrancó perfectamente (pulsa en la imagen para ampliar):

tablet_booted

Ahora voy a probar a compilar mi propio núcleo y a intentar arrancarlo desde la tarjeta microSD.

Tamaño constante en GTK

Hace unos días envié un parche para Slingshot, el lanzador de aplicaciones de Elementary OS. El problema que tenía es que tenía fijados al pixel los tamaños de todos los elementos. Esto se adapta bien si se utiliza el tema de Elementary y, además, en lengua inglesa. Sin embargo, en el momento en que se utiliza otro tema, o bien una lengua en la que alguna categoría de programas sea mucho más larga que en inglés, nos encontraremos con que el conjunto de iconos queda recortado por la derecha:

Se supone que esto no debería ocurrir porque Gtk permite crear interfaces que se redimensionen de manera automática, sin embargo en slingshot esto no ocurría. El motivo es que los iconos no están hechos con un Gtk.IconView, sino con botones dentro de un Gtk.Grid. Pero los autores querían, además, que dichos botones tuviesen un tamaño fijo de 130×130 pixels exactamente, por lo que la única idea que se les ocurrió fue fijar el tamaño exacto de la ventana que contiene todo el lanzador, incluyendo la lista de categorías, el buscador, etc. El motivo de hacerlo así es que dentro de cada botón hay, además del icono, una etiqueta con el nombre de la aplicación, y salvo que el contenedor tenga un tamaño determinado, la etiqueta intentará expandirse todo lo que pueda, y ni siquiera sirve de nada pedir un tamaño deseado, porque si el texto en la etiqueta es más largo, la etiqueta se expandirá todo lo que pueda para contener todo el texto.

La primera idea que se me ocurrió fue engancharme a la señal size_allocate del botón. Esta señal se emite cada vez que un widget cambia de tamaño por el motivo que sea, y recibe como parámetro sus nuevas medidas. Con ella y la propiedad max_width_chars intenté limitar el tamaño de la etiqueta para que el botón tuviese el tamaño máximo deseado; por desgracia era un proceso relativamente lento y que, además, provocaba un parpadeo en la ventana, pues ésta primero tomaba el valor máximo y se reducía progresivamente hasta el tamaño correcto, a medida que las distintas etiquetas iban reduciendo su número de caracteres.

La segunda idea, sugerida por la gente de la lista de desarrollo de Gtk, fue encapsular la etiqueta dentro de un Gtk.Fixed, pues permite ajustar manualmente el tamaño de cualquier widget situado en su interior. Esta solución casi funcionó, pues aunque la etiqueta se mantenía en el ancho deseado, el Gtk.Fixed contenedor se redimensionaba hasta ocupar el mismo espacio que ocuparía la etiqueta si estaba sola.

Ante esto me di cuenta de que la solución iba a necesitar de algo más de investigación y, sobre todo, de meterme en los entresijos del renderizado de Gtk, así que empecé a investigar, y descubrí que todo widget tiene dos métodos que participan de manera especial en la asignación de tamaño: get_preferred_width y get_preferred_height. Estos dos métodos devuelven dos valores: el primero es el tamaño mínimo que necesita dicho widget en el eje correspondiente para mostrar su contenido, y el segundo el tamaño deseado. En el caso de una etiqueta, el tamaño deseado será el ancho que ocupe la totalidad del texto puesto en una única línea, mientras que el tamaño mínimo dependerá de varias opciones: si no hay elipsis de texto coincidirá con el tamaño deseado, pero si no, será menor, de lo necesario para pintar el borde y unos puntos suspensivos.

Cuando un contenedor tiene en su interior otro widget, llama a estos dos métodos para saber cuanto espacio debe reservar. Lo interesante es que dichos valores son, realmente, orientativos, pues el contenedor puede asignar otros tamaños si así lo considera oportuno. Así, si una etiqueta pide 100 pixels pero está en una ventana que mide 200 y tiene activo el flag de expandirse, el tamaño final de la etiqueta será de 200. De igual forma, si una etiqueta pide un tamaño deseado de 100 pixels de ancho pero la ventana mide 50, recibirá 50 y el texto tendrá que ocupar varias líneas.

Ante esto se me ocurrió probar qué ocurriría si creo una nueva clase que herede de Gtk.Label, en la que redefino (override) el método get_preferred_width para que devuelva 120 como ancho mínimo y deseado (porque cada botón tiene un margen de 5 pixels por cada lado). El resultado es justo el deseado: la etiqueta mide exactamente el ancho que se le indica, y aplica las reglas para pasar a nuevas líneas o para añadir elipsis.

Por supuesto esto es fácil de hacer en Vala; en C es posible hacerlo también, pero obliga a meterse en el interior de GObject, una tarea que, sinceramente, no me llama demasiado en este momento.

Cagándola con un bug grave

A veces a uno se le escapan pequeños bug que, en principio, no tienen la menor importancia, hasta que resulta que sí la tienen. Eso me ha ocurrido con la nueva versión en desarrollo de DevedeNG.

El bug ni siquiera era tal, sino más bien una combinación de valores por defecto poco adecuados, mensajes poco claros, y código que hacía demasiadas cosas. Básicamente, DevedeNG necesita una carpeta vacía donde crear el proyecto, así que le pide al usuario que le indique una. Si dicha carpeta ya existe le indica que va a proceder a borrarla, dejándole escoger si quiere continuar o cancelar antes de hacerlo.

Hasta aquí todo parece normal. El problema es que la carpeta por defecto era la carpeta personal del usuario, y el mensaje de aviso decía simplemente: «La carpeta seleccionada ya existe. Si continúa, será borrada. ¿Continuar?». Este mensaje no es lo suficientemente claro porque en ninguna parte aparece qué carpeta se va a borrar. Ante esto, un usuario que se limite a pulsar Siguiente->Siguiente->Siguiente se encontrará con su carpeta personal borrada.

El código ya está corregido, para lo que he aplicado las siguientes lecciones aprendidas:

  • Debe evitarse utilizar la carpeta de usuario ($HOME) como destino por defecto cuando haya que realizar alguna acción que borre una carpeta. En el caso de DevedeNG, el primer cambio fue que la carpeta destino se componga de la carpeta escogida por el usuario más el nombre del proyecto, por lo que, por defecto, nunca será la carpeta de usuario sola la escogida para ser borrada.
  • Siempre que se le pida al usuario confirmación para borrar una carpeta debe mostrarse claramente en el texto qué carpeta se va a borrar. Nunca se debe dar por supuesto que el usuario lo sabe.
  • En la medida de lo posible no debe borrarse la carpeta al completo, sino buscar qué ficheros y directorios son los que pueden interferir con el proceso que se quiere realizar, borrando exclusivamente esos y ninguno más.

Afortunadamente, el usuario que se tropezó con este problema tenía copia de seguridad, por lo que todo quedó en un susto.

Cascos inalámbricos

Hace tiempo me compré unos cascos inalámbricos, en concreto el modelo MDR-RF810R de Sony. Mi intención era utilizarlos con el ordenador, para olvidarme de los molestos cables.

Los cascos y el transmisor

Por desgracia, sólo puedo utilizarlos con la televisión porque tienen un serio defecto: a los cuatro minutos de no recibir señal acústica la base transmisora se apaga (supuestamente para ahorrar energía… lo que es ridículo porque no usa pilas sino un transformador; son los cascos en sí los que utilizan baterías, y ellos nunca se desconectan). En ese momento los cascos pegan un petardazo muy fuerte. Con la tele no es un problema porque cuando termino de verla, la apago y me quito los cascos, pero con el ordenador puedo ver un vídeo y, al terminar, seguir leyendo o trabajando con los cascos puestos, por lo que cuatro minutos después, estando todo en pleno silencio, los cascos me sueltan el petardazo sin previo aviso. Afortunadamente no es muy difícil resolver este problema, así que en esta entrada voy a explicar como lo hice.

Una mirada al interior

Al desmontar el transmisor nos encontramos con este circuito.

Un circuito bastante sencillo

A primera vista parece muy sencillo: un par de transistores que, probablemente, amplifiquen un poco la entrada, algunos condensadores de filtrado, y una caja metálica que, seguramente, contiene la parte de alta frecuencia. Pero en realidad tiene truco, porque si le damos la vuelta nos encontramos con el verdadero circuito:

Parece que hablamos demasiado pronto…

El primer gran problema que nos encontramos es que los números de los chips están borrados. Afortunadamente hay uno que sí se puede leer más o menos, y que resulta ser un chip con cuatro amplificadores operacionales. Analizando el circuito que lo acompaña se nota cierta estructura simétrica, lo que nos hace sospechar que puede tener que ver con algún tipo de proceso de señal estéreo.

Tras analizar con cuidado todo, llegamos a la conclusión de que la señal de sonido se divide en dos partes: por un lado se envía directamente al chip de la derecha, que resulta ser un modulador de FM, y por otro se envía a ese circuito que vimos antes, donde se detecta si hay o no señal. La salida del modulador de FM es una señal en banda base modulada en estéreo, la cual se pasa al transmisor localizado dentro de la caja metálica en el otro lado de la placa, que sube la frecuencia hasta la banda deseada y la envía a la antena. Este transmisor recibe su alimentación a través de un regulador de tensión diferente al del resto de la circuitería, el cual, además, está controlado desde el modulador de FM para poder encenderlo y apagarlo a voluntad.

Al principio se me ocurrió que si forzaba la alimentación del transmisor, de manera que siempre estuviese encendido, resolvería el problema. Por desgracia no funcionó. Sospecho que es porque se trata de un simple elevador de frecuencia, por lo que si no hay una señal desde el modulador de FM, la salida sigue siendo nula. Ante esto llevé mi investigación al resto del circuito.

Bloques principales del transmisor FM

El detector/amplificador de pulsos detecta cuando hay señal de audio en la entrada del transmisor, momento en que activa el transistor de conmutación. De esta manera éste conecta su salida a masa cuando hay una señal de audio, o la deja flotante cuando no la hay, en cuyo caso una resistencia de pull-up fija una tensión de entre 4,8 y 5,6 voltios. Esta salida está conectada directamente al modulador de FM (línea naranja), y es la que reinicia el contador de cuatro minutos de apagado cada vez que se detecta señal.

La deducción lógica de todo esto es que, para que el transmisor no se desactive nunca, basta con cortocircuitar dicho transistor, de manera que su salida esté siempre a cero voltios, haciendo creer así al modulador FM que hay una señal de audio en todo momento. Para ello basta con cortocircuitar los dos pines marcados en la imagen de abajo con la línea rosa (el círculo rodea al transistor de conmutación):

El circulo naranja rodea al transistor de conmutación; el trazo rosa indica los dos pines que hay que cortocircuitar

Y con esto resolvemos el problema y ya podemos usar los cascos sin miedo a sobresaltos.

Mejorando Debian sobre Android

En la entrada anterior expliqué como hice para arrancar una Debian en mi dispositivo AndroidTV. Hoy estuve retocando el código para hacerlo más flexible y corregir algunos problemas de tipo práctico que he encontrado al empezar a usarlo de manera regular.

Cambios en launch_debian

El primer cambio ha sido en el propio launch_debian. Este binario es el encargado de detectar cuando se enchufa un disco externo y de lanzar la aplicación runlinux.sh que esté en su directorio raíz. El primer problema que resolví en ella es permitir pasarle los directorios en donde montar la unidad externa, en donde buscar los dispositivos conectados, y el tipo de sistema de archivos que se quiere montar. Todo esto se puede especificar ahora desde la línea de comandos si se desea (si no se indica nada, se utilizan los valores por defecto que comenté en la anterior entrada).

El segundo cambio es añadir una FIFO para poder ejecutar comandos desde el entorno externo al chroot. El principal motivo es para poder ejecutar un apagado ordenado, pues desde dentro del entorno no se puede ejecutar, por ejemplo, un shutdown -h now. Para ello se crea la FIFO /dev/chroot_ext_control, que esperará a recibir algún comando. De momento admite dos:

  • halt mata todos los procesos que estén accediendo a algún fichero en la unidad, y una vez hecho esto, la vuelve a montar como sólo-lectura, para que no se corrompan los datos al apagar.
  • reboot lo mismo que halt, pero luego reinicia la máquina

Estos comandos se pueden emitir simplemente con echo reboot > /dev/chroot_ext_control o echo halt > /dev/chroot_ext_control.

El nuevo código se puede descargar con este enlace.

Los parámetros que acepta la nueva versión desde la línea de comandos son:

  • -o opciones de montaje. La cadena que sigue se añade tal cual al comando mount, junto con -o. Esto permite añadir opciones como noatime.
  • -m ruta de montaje. La cadena que sigue indica la ruta donde se montará la unidad externa. Por defecto es /system/debian.
  • -t tipo de sistema de ficheros. La cadena que sigue indica el tipo de sistema de ficheros que se pasará a mount. Por defecto es ext4.
  • -c ruta de dispositivos. La cadena que sigue indica la ruta donde aparecen los ficheros de dispositivo, como sda1. Por defecto es /dev/block.
  • -f ruta y fichero para la FIFO. La cadena que sigue indica la ruta y el fichero para la FIFO de control. Por defecto es /dev/chroot_ext_control.

El motivo de poner la FIFO de control en /dev es que, al ser un sistema de ficheros en RAM, no habrá interferencias con otros posibles ficheros, y además es accesible con el mismo nombre desde dentro y fuera del entorno chroot.

Cambios en el lanzador principal

El script situado en /system/etc/install-recovery.sh también ha cambiado ligeramente. Ahora es así:

#!/system/bin/sh

cp /system/bin/launch_debian /dev
/dev/launch_debian &

El motivo de hacer esto es que ahora launch_debian ya no muere al lanzar el entorno Debian, sino que sigue en marcha para responder a las peticiones de comandos emitidas a través de la FIFO. Eso significa que si queremos actualizarlo no lo podemos hacer directamente, porque el fichero estará bloqueado. Por eso primero copiamos el ejecutable a /dev (que tiene la ventaja de ser un disco RAM, y dado que el ejecutable es muy pequeño, no supone un desperdicio de memoria) y lo ejecutamos desde ahí: de esa forma el ejecutable original nunca se bloquea, y podemos reemplazarlo en caliente y luego reiniciar para que se utilice la nueva versión.

Cambios en el lanzador de la jaula chroot

El script situado en el disco duro, runlinux.sh, también ha cambiado un poco. Ahora es así:

#!/system/bin/sh

# stop the Android system
stop
sleep 1
# stop the daemons to ensure that
# they don't disturb the debian system
# (can't kill them because INIT would
#  relaunch them)
# Also allows to send them to SWAP
busybox killall -SIGSTOP netd
busybox killall -SIGSTOP vold
busybox killall -SIGSTOP displayd
busybox killall -SIGSTOP ueventd
busybox killall -SIGSTOP debuggerd
busybox killall -SIGSTOP rild
busybox killall -SIGSTOP drmserver
busybox killall -SIGSTOP mediaserver
busybox killall -SIGSTOP installd
busybox killall -SIGSTOP servicemanager
# undo changes to kernel variables
echo 0 > /proc/sys/kernel/panic_on_oops
echo 18000000 > /proc/sys/kernel/sched_latency_ns
echo 3000000 > /proc/sys/kernel/sched_wakeup_granularity_ns
echo 0 > /proc/sys/vm/overcommit_memory
echo 3000 > /proc/sys/vm/dirty_expire_centisecs
echo 1024 > /dev/cpuctl/apps/bg_non_interactive/cpu.shares
echo 900000 > /dev/cpuctl/apps/cpu.rt_runtime_us
echo 900000 > /dev/cpuctl/apps/bg_non_interactive/cpu.rt_runtime_us
# mount proc, sys, dev, dev/pts, dev/cpuctl and /system
mount -o bind /proc $1/proc
mount -o bind /sys $1/sys
mount -o bind /dev $1/dev
mount -o bind /dev/pts $1/dev/pts
mount -o bind /dev/cpuctl $1/dev/cpuctl
mount -o bind /system $1/android
export HOME=/root
export LD_LIBRARY_PATH=
export PATH=/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
# set the Framebuffer devices where standard apps expect them
cp -a /dev/graphics/* /dev
# launch our Debian system
/system/bin/busybox chroot $1 /bin/system.sh

El primer cambio está en los distintos comandos echo XXXXXX >…. Estos comandos pretenden restaurar los valores originales en diversas variables del núcleo. Esto es porque Android está orientado hacia aplicaciones de usuario, por lo que, por ejemplo, le quita prioridad a las aplicaciones en segundo plano. Con estas opciones intento deshacer lo que se cambia en el fichero init.rc. Sin embargo es importante indicar que probablemente lo que cambie dependa de cada fabricante, así que cada uno debe mirar qué es lo que se modifica.

El siguiente cambio es que monto también /dev/cpuctl, algo que se me había pasado.

Por último, monto el directorio /system dentro del directorio android de la unidad montada. De esta manera se tiene acceso al sistema Android desde dentro de la jaula chroot, lo que permite, por ejemplo, actualizar el fichero launch_debian.