No quiero tener envidia

Comencé a armar mis propias computadoras (que no fueran laptops) hace más o menos quince años. Casi desde la primera que tuve la oportunidad de decidir qué tendría, le puse una tarjeta de video Nvidia; lo hice de manera impulsiva, porque entonces todavía estaba verde, y no verifiqué que la tarjeta estuviera soportada por Linux. De manera fortuita, Nvidia (la compañía) sacó justo por esos tiempos su primer controlador para Linux, lo que me salvó la vida porque de otra manera no hubiera podido correr X. Era una Nvidia RIVA TNT2, que me imagino muchos de mis contemporáneos conocieron.

A partir de ese momento todas mis computadoras de escritorio (y varias de mis laptops) tuvieron tarjeta de video Nvidia; y cada vez que compré una nueva, era mucho más poderosa que la anterior. He de haber tenido del orden de 5 o 6 tarjetas Nvidia; la última fue una GT 8800, que en su momento era ruda, aunque no de las más rudas.

Como estoy trabajando de nuevo (quiero decir, además de dar clases), y me están pagando relativamente bien, decidí actualizar mi computadora de escritorio, que hacía años no la había modificado. Por primera vez en mi vida, armé la computadora sin una tarjeta de video Nvidia.

Las razones son múltiples; en mis varias estancias de investigación en Europa, Canadá y Estados Unidos, trabajé únicamente con laptops, que desde hace años he comprado únicamente con las tarjetas de video Intel integradas, porque con tarjetas de video Nvidia el precio aumentaba considerablemente, y la vida de la batería disminuía casi en la misma proporción. Rápidamente comencé a apreciar que en Linux las cosas de Intel generalmente funcionan sin que uno tenga que hacer absolutamente nada, y que su desempeño ha ido mejorando lenta, pero inexorablemente.

En cambio los controladores binarios de Nvidia han tenido un comportamiento errático desde hace años; de repente funcionan de manera impecable, para luego comenzar a dar broncas que es medio imposible descifrar. Su tamaño ha también aumentado de forma ridícula a lo largo de los años; la última versión mide 67 megabytes comprimidos, y un montón de eso termina ejecutándose en la memoria del kernel.

Que fue la otra cosa que comenzó a molestarme; durante más de una década le he estado dando mi dinero a Nvidia, y la compañía no ha hecho nada en lo más mínimo para abrir el código de sus tarjetas a los programadores de Linux (lo que llevó a Linus a decirle a Nvidia que chingaran a su madre).

Así que ahora que actualicé mi máquina, decidí que suficiente era suficiente, y decidí utilizar el GPU integrado de Intel en mi Core i7.

La verdad, estoy encantado. Al igual que en mis laptops, funciona de pelos sin que yo tuviera que hacer nada, y el desempeño es igual (y en algunos casos, mejor) que el de Nvidia para mi escritorio GNOME. Me refiero al uso normal del escritorio, incluyendo reproducción de video y las ligeras animaciones que se incluyen. Para OpenGL en bruto, sin duda Nvidia le gana; pero yo realmente ya no juego en mi PC, sólo en mi PlayStation 3, así que no es gran pérdida.

Y desde el punto de vista tecnológico aparentemente no importa, pero ideológicamente sí me alegra que mis computadoras Linux ya todas tienen controladores que son software libre. Y digo que en lo tecnológico es aparente que no importa, porque me parece que al final sí importa; hace años que no tengo ningún problema con mis tarjetas de video Intel, y no puedo decir lo mismo de las Nvidia.

Si por alguna razón llego a necesitar poder OpenGL en bruto, voy a comprarme una Radeon (que también ofrece controladores de software libre), y si sólo necesito OpenGL “normal”, Intel me basta y sobra.

A partir de ahora, ya no quiero tener envidia.

Como Pedro por su casa

Como comentaba hace unos días, leí de nuevo Cien años de soledad. El punto de esta entrada es cómo lo leí; inicialmente no pensaba leerlo, sólo quería la cita que andaba buscando. Lo que hice fue lo que he hecho en otras ocasiones: me puse a buscar un PDF pirata de la novela.

He comprado Cien años de soledad varias veces en mi vida, generalmente para regalarlo, porque me parece trágico cuando encuentro a alguien que no la ha leído; no siento el menor remordimiento de conciencia de también haberlo conseguido (en múltiples ocasiones) de forma pirata en PDF, generalmente justo porque quiero una cita exacta.

Pero esta vez no pude, así que me metí a Google Play y lo compré en 89 pesotes. No me arrepiento en lo más mínimo; si hay libros que quiero tener siempre disponibles en mis dispositivos ligados a mi cuenta de Google, Cien años de soledad es sin duda alguna el primero en la lista. Me sacó un poco de onda que le piqué a “Comprar”, y Google lo hizo de inmediato, sin en ningún momento pedir mis datos o una contraseña. Ya había comprado antes ahí, así que no es que sacaran los datos de mi tarjeta de crédito del éter; pero de todas maneras fue ligeramente desconcertante picarle “Comprar”, y que Google alegremente me informara que ya tenía un nuevo libro en mi tableta, y que el cargo correspondiente a mi tarjeta de crédito ya se había realizado. Se metió a mi cuenta de banco como Pedro por su casa.

No me preguntó ninguna contraseña, ningún dato, un CCV, nada. Sólo de repente Google agarró y se metió en mi cuenta del banco y dijo “banco, dame dinero”, y el banco dijo “chingón”, y yo quedé 89 pesos más pobre, pero con una versión digital bastante bonita de la obra maestra de Gabo.

Como ya dije, no me arrepiento. Sólo no me gusta tanto la idea de que gastar dinero sea tan sencillo en un dispositivo amarrado a mi tarjeta de crédito.

El lector de libros digitales de Google está poca madre; en mi Nexus 7 al pasar las páginas, hay una animación como si uno literalmente pasara la página. Hay marcadores, se pueden hacer anotaciones, búsquedas, y hasta donde pude ver no tenía un solo error la edición; lo más que puedo quejarme es que de repente una “página” se quedaba en blanco a la mitad, para continuar en la siguiente. No tengo idea de por qué; un error en el algoritmo espaciador de párrafos, me imagino.

Leer en el Nexus 7 es la neta; mucho más ligero que un libro y sin problemas de iluminación (ya sea en la calle o bajo techo). Por supuesto ya había leído PDFs antes; pero este es el primer libro digital que de hecho compré, y debo admitir que me apantalló bastante. Estoy pensando seriamente comprar otros libros usando Google Play; lamentablemente, muchos de los que me interesan son en inglés, y no tengo ni puta idea de cómo conseguirlos en su idioma original. Google Play sólo me ofrece versiones en español.

Si logro resolver eso, voy a comenzar a hacerme de una copiosa biblioteca digital.

La colección de trofeos

(Esta entrada es la octava parte de una serie que cubre un proyecto personal que realicé en el verano de 2014; pueden ver todas las partes aquí).

Mi hermano me regaló mi PlayStation 3 hace casi seis años en 2008. Lo primero que hice fue ir y comprar GTA4, como lo relaté en esta entrada. Al final de la misma comentaba que me interesaba comprar Rock Band o Guitar Hero, y de hecho la actualicé (no sé exactamente cuánto después, pero debieron ser un par de días) para comentar que ya había comprado una guitarra de plástico y el primer Rock Band.

Al inicio no utilicé mucho mi PS3; avancé bastante en GTA4 (aunque no lo acabé), y jugué mucho Rock Band (aunque básicamente jugando nada más las canciones que conocía y me gustaban), pero un par de meses después me fui a mi primera estancia de investigación en Europa, y mi PlayStation 3 acumuló polvo durante tres meses.

Cuando regresé a México, mi PS3 pasó a ser usado principalmente como reproductor de películas Blu-ray durante los próximos dos años; continué jugando Rock Band, y terminé la historia de GTA4 (después de reiniciarlo porque ya se me había olvidado lo que llevaba), pero sin duda alguna lo que más hice con mi consola fue ver películas.

En retrospectiva, esto es por la naturaleza de cómo me gusta consumir medios: estoy básicamente loco. Lo cual por supuesto no debería sorprender a nadie.

Me gusta consumir cultura popular de forma ligeramente obsesiva; tengo que leer todos los libros de una serie (Harry Potter, Dunas, el legendario de Tolkien, la serie de Fundación de Asimov, Terramar de Ursula K. Le Guin, y un largo etcétera); tengo que ver todas las películas de una serie (demasiadas como para listarlas); tengo que ver todos los capítulos de una serie de televisión (ídem); etc., etc.

No me queda claro por qué hago esto; sencillamente así es. Nota interesante; es posible que esta sea la razón por la cual la música no juega un papel más importante en mi vida: casi por definición, su naturaleza es no serial, y generalmente no tiene una “historia” no trivial. Lo que sí, es que cuando una canción me gusta, suelo escucharla unas catorce millones de veces seguidas antes de pasar a otra.

Ahora, muchos videojuegos (incluyendo varios que me han encantado) tienen una única parte, así que a primera vista no es trivial encontrar la conexión entre ellos y mi consumo de medios seriales.

A lo mejor estoy inventándome cosas, o intelectualizando alguna otra obsesión mía, pero creo que el equivalente que tengo con videojuegos es que me gusta jugar todo el videojuego. Me explico: los videojuegos modernos son (en general) tan grandes, que es posible (y de hecho ocurre casi siempre) terminar la historia, sin haber descubierto gran parte del videojuego. Aunque viene ocurriendo desde hace décadas; Super Mario Bros. y muchos de sus contemporáneos tenían niveles secretos, easter eggs, y otras cosas del estilo.

Recuerdo claramente cuando acabé la historia de GTA 4, que me quedé con la impresión de que quedaba muchísimo por descubrir de Liberty City, y que no sabía exactamente cómo continuar. Y no digamos en Rock Band, donde para motivos prácticos no hay historia.

Supongo que pude haber continuado así el resto de mi vida, usando principalmente mi PS3 para ver películas, y jugando de vez en cuando uno que otro GTA o juego musical, hasta un día que en su casa, Juan me enseñó el demo de God of War III. Es chistoso, porque todas mis manías se conjugaron para que yo acabara persiguiendo trofeos.

Como comenté en otra entrada, después de que Juan me enseñara el demo, yo llegando a mi casa lo bajé y casi casi lo memoricé. Es un gran juego, así que decidí que lo compraría y lo jugaría y lo acabaría.

Sólo había un pequeño detalle. Era God of War III… yo no podía ponerme a jugar la tercera parte del juego si no había jugado las otras dos antes. Por… razones.

Así que fui a comprar GoW1, que por suerte venía en paquete con GoW2. Me chuté el primero, y me encantó, así que lo jugué de hecho varias veces, y luego me eché los retos de los dioses, y cuando me di cuenta, me faltaban un par de trofeos en el juego para obtener mi platino. Mi primer platino. Y luego me chuté GoW2, y obtuve mi segundo platino.

Y entonces vi una manera de acallar a las voces en mi cabeza que exigen sacrificios paganos si no consumo algo al 100%: los trofeos, en general, me permiten disfrutar un juego al 100%; ver todos sus secretos, ver toda la historia, y matar hasta la última paloma. No voy a explicar eso último.

A partir de ese momento, decidí que obtendría el 100% de los trofeos de todos mis juegos, así me llevara años. Y luego salí de México, por lo que de hecho comenzar a hacer esto tuvo que esperar seis meses.

Debo hacer hincapié que yo no me considero trophy hunter. No me he puesto a comprar juegos facilísimos sólo para poder tener más trofeos de platino (al parecer a Hannah Montana: The Movie es muy fácil sacarle el platino), ni he dejado de jugar juegos sólo porque sus trofeos sean muy difíciles (tengo el 100% de trofeos en GTA4 y WipeOut HD; todo mundo está de acuerdo en que son de los juegos más difíciles de completar sus trofeos).

Sí me he negado a jugar algunos juegos porque ya no se pueden sacar el 100% de trofeos; FIFA 09, que venía con mi PS3, es un ejemplo: los servidores en línea dejaron de funcionar en 2011, así que sencillamente no tengo un solo trofeo de ese juego. Que la verdad, fue suerte borracha; según yo sí lo llegué a jugar, pero por alguna razón nunca se registró un trofeo del mismo en mi cuenta. A lo mejor sólo fui muy malo al jugar.

Como sea, los juegos que no se pueden completar sus trofeos son en general pocos, y además de hecho no me interesan mucho.

No me llevó mucho tiempo descubrir que alcanzar el 100% de trofeos en mis juegos era no sólo posible, sino de hecho humanamente realizable. Podía completar el 100% de mi colección trofeos con algo de esfuerzo, en todos y cada uno de mis juegos.

Excepto por los musicales. Pero de esos escribiré luego.

12ba:0200

(Esta entrada es la séptima parte de una serie que cubre un proyecto personal que realicé en el verano de 2014; pueden ver todas las partes aquí).

El dispositivo USB-HID con identificador de vendedor 0x12ba e identificador de producto 0x0200 (y que por lo tanto aparece como 12ba:0200 al invocar lsusb) tiene el siguiente reporte descriptivo en C:

0x05, 0x01,       /* Usage Page (Generic Desktop Controls) */
0x09, 0x05,       /* Usage (Gamepad) */
0xa1, 0x01,       /* Collection (Application) */
0x15, 0x00,       /*   Logical Minimum (0) */
0x25, 0x01,       /*   Logical Maximum (1) */
0x35, 0x00,       /*   Physical Minimum (0) */
0x45, 0x01,       /*   Physical Maximum (1) */
0x75, 0x01,       /*   Report Size (1) */
0x95, 0x0d,       /*   Report Count (13) */
0x05, 0x09,       /*   Usage Page (Button) */
0x19, 0x01,       /*   Usage Minimum (Button 1) */
0x29, 0x0d,       /*   Usage Maximum (Button 13) */
0x81, 0x02,       /*   Input 2 (Data, Variable, Abs) */
0x95, 0x03,       /*   Report Count (3) */
0x81, 0x01,       /*   Input 1 (Data, Var, Abs) */
0x05, 0x01,       /*   Usage Page (Generic Desktop) */
0x25, 0x07,       /*   Logical Minimum (7) */
0x46, 0x3b, 0x01, /*   Physical Maximum 315 */
0x75, 0x04,       /*   Report Size (4) */
0x95, 0x01,       /*   Report Count (1) */
0x65, 0x14,       /*   Unit 20 (English rotation, degrees) */
0x09, 0x39,       /*   Usage (Hat switch) */
0x81, 0x42,       /*   Input 66 (Data, Var, Abs,Null) */
0x65, 0x00,       /*   Unit (None) */
0x95, 0x01,       /*   Report Count 1 */
0x81, 0x01,       /*   Input (Const, Array, Abs) */
0x26, 0xff, 0x00, /*   Logical Maximum 255 */
0x46, 0xff, 0x00, /*   Physical Maximum 255 */
0x09, 0x30,       /*   Direction-X 48 */
0x09, 0x31,       /*   Direction-Y 49 */
0x09, 0x32,       /*   Direction-Z 50 */
0x09, 0x35,       /*   Rotate-Z 53 */
0x75, 0x08,       /*   Report Size 8 */
0x95, 0x04,       /*   Report Count 4 */
0x81, 0x02,       /*   Input 2 (Data, Var, Abs) */
0x06, 0x00, 0xff, /*   Usage Page 65280 (null) */
0x09, 0x20,       /*   Usage 32 (null) */
0x09, 0x21,       /*   Usage 33 (null) */
0x09, 0x22,       /*   Usage 34 (null) */
0x09, 0x23,       /*   Usage 35 (null) */
0x09, 0x24,       /*   Usage 36 (null) */
0x09, 0x25,       /*   Usage 37 (null) */
0x09, 0x26,       /*   Usage 38 (null) */
0x09, 0x27,       /*   Usage 39 (null) */
0x09, 0x28,       /*   Usage 40 (null) */
0x09, 0x29,       /*   Usage 41 (null) */
0x09, 0x2a,       /*   Usage 42 (null) */
0x09, 0x2b,       /*   Usage 43 (null) */
0x95, 0x0c,       /*   Report Count 12 */
0x81, 0x02,       /*   Input 2 (Data, Var, Abs) */
0x0a, 0x21, 0x26, /*   Usage 9761 (null) */
0x95, 0x08,       /*   Report Count 8 */
0xb1, 0x02,       /*   Feature 2 (Data, Var, Abs) */
0x0a, 0x21, 0x26, /*   Usage 9761 (null) */
0x91, 0x02,       /*   Output 2 (Data, Var, Abs) */
0x26, 0xff, 0x03, /*   Logical Maximum 1023 */
0x46, 0xff, 0x03, /*   Physical Maximum 1023 */
0x09, 0x2c,       /*   Usage 44 (null) */
0x09, 0x2d,       /*   Usage 45 (null) */
0x09, 0x2e,       /*   Usage 46 (null) */
0x09, 0x2f,       /*   Usage 47 (null) */
0x75, 0x10,       /*   Report Size 16 */
0x95, 0x04,       /*   Report Count 4 */
0x81, 0x02,       /*   Input 2 (Data, Var, Abs) */
0xc0              /* End_Collection */

Como lo hice con el DualShock 3, veámoslo por partes.

0x05, 0x01,       /* Usage Page (Generic Desktop Controls) */
0x09, 0x05,       /* Usage (Gamepad) */
0xa1, 0x01,       /* Collection (Application) */

Comienza igual que el DualShock 3, la única diferencia es que se identifica como gamepad, no como joystick. La verdad en estos días, para una computadora (incluyendo al PlayStation 3), no existe realmente diferencia entre joystick y gamepad; ambos tienen 2 o más ejes, y un montón de botones.

0x15, 0x00,       /* Logical Minimum (0) */
0x25, 0x01,       /* Logical Maximum (1) */
0x35, 0x00,       /* Physical Minimum (0) */
0x45, 0x01,       /* Physical Maximum (1) */
0x75, 0x01,       /* Report Size (1) */
0x95, 0x0d,       /* Report Count (13) */
0x05, 0x09,       /* Usage Page (Button) */
0x19, 0x01,       /* Usage Minimum (Button 1) */
0x29, 0x0d,       /* Usage Maximum (Button 13) */
0x81, 0x02,       /* Input 2 (Data, Variable, Abs) */

Esta parte codifica 13 botones (“Report Count (13)”) binarios (“Report Size (1)”), no “analógicos”; o sea, el dispositivo sólo avisa si uno de estos botones está o no apachurrado, no da información acerca de qué tanto lo está apretando el usuario.

0x95, 0x03,       /* Report Count (3) */
0x81, 0x01,       /* Input 1 (Data, Var, Abs) */

Luego el dispositivo mete tres bits de relleno (padding); de esta manera, los primeros dos bytes que envía el dispositivo (13 + 3 = 16 bits = 2 bytes), llevan la información de qué botones están o no presionados. Obviamente, 1 (bit prendido) significa que el botón está apretado, y 0 (bit apagado) que no lo está.

0x05, 0x01,       /* Usage Page (Generic Desktop) */
0x25, 0x07,       /* Logical Minimum (7) */
0x46, 0x3b, 0x01, /* Physical Maximum 315 */
0x75, 0x04,       /* Report Size (4) */
0x95, 0x01,       /* Report Count (1) */
0x65, 0x14,       /* Unit 20 (English rotation, degrees) */
0x09, 0x39,       /* Usage (Hat switch) */
0x81, 0x42,       /* Input 66 (Data, Var, Abs,Null) */
0x65, 0x00,       /* Unit (None) */
0x95, 0x01,       /* Report Count 1 */
0x81, 0x01,       /* Input (Const, Array, Abs) */
0x26, 0xff, 0x00, /* Logical Maximum 255 */
0x46, 0xff, 0x00, /* Physical Maximum 255 */

Luego vienen 4 bits (“Report Size (4)”, “Report Count (1)”) que codifican el “hat switch“, la crucecita que tienen casi todos los gamepads que sirven para mover a Mario a la derecha, izquierda, que se agache, o que se meta al tubo del techo después de brincar.

Siendo yo programador, yo hubiera esperado que cada bit codificara una de las cuatro direcciones; algo como 0001 = norte, 0010 = sur, 0100 = este, 1000 = oeste, y 0000 que el usuario no está tocando ninguna… o algo del estilo. Esto también permitiría las combinaciones pertinentes: 0101 sería noreste, etc.

Los ingenieros que diseñaron esto, sin embargo, se les ocurrió que lo que tenía sentido es que 0000 fuera norte, 0001 noreste, 0010 este, 0011 sureste, 0100 sur, 0101 suroeste, 0110 este, 0111 noreste, y 1000 nada presionado. Siendo honesto, la verdad esto puede venir desde el estándar USB-HID, pero como no lo leí completo, no tengo idea. De cualquier forma, me suena a algo que se le ocurriría a un ingeniero.

Al definir el máximo como 255, se le asignan 8 bits al hat switch, así que 4 de ellos quedan también como relleno.

0x09, 0x30,       /* Direction-X 48 */
0x09, 0x31,       /* Direction-Y 49 */
0x09, 0x32,       /* Direction-Z 50 */
0x09, 0x35,       /* Rotate-Z 53 */
0x75, 0x08,       /* Report Size 8 */
0x95, 0x04,       /* Report Count 4 */
0x81, 0x02,       /* Input 2 (Data, Var, Abs) */

Parecido al DualShock 3, este gamepad define dos joysticks con dos ejes cada uno, usando 4 bytes para enviar su estado. Hasta donde he podido ver, el dispositivo 12ba:0200 no hace uso del primer joystick, enviando siempre la información correspondiente a como si estuviera centrado. El segundo joystick sí es usado; diré cómo más adelante.

0x06, 0x00, 0xff, /* Usage Page 65280 (null) */
0x09, 0x20,       /* Usage 32 (null) */
0x09, 0x21,       /* Usage 33 (null) */
0x09, 0x22,       /* Usage 34 (null) */
0x09, 0x23,       /* Usage 35 (null) */
0x09, 0x24,       /* Usage 36 (null) */
0x09, 0x25,       /* Usage 37 (null) */
0x09, 0x26,       /* Usage 38 (null) */
0x09, 0x27,       /* Usage 39 (null) */
0x09, 0x28,       /* Usage 40 (null) */
0x09, 0x29,       /* Usage 41 (null) */
0x09, 0x2a,       /* Usage 42 (null) */
0x09, 0x2b,       /* Usage 43 (null) */
0x95, 0x0c,       /* Report Count 12 */
0x81, 0x02,       /* Input 2 (Data, Var, Abs) */

Aquí es donde esto se pone interesante; todo ese relajo (que lsusb en Linux no puede reconocer, y por lo tanto yo le puse “null”), codifica 12 bytes que, hasta donde yo adivino, deberían codificar qué tanto están presionados 12 de los 13 botones que reportan los primeros 2 bytes. Deberían siendo la palabra clave.

0x0a, 0x21, 0x26, /* Usage 9761 (null) */
0x95, 0x08,       /* Report Count 8 */
0xb1, 0x02,       /* Feature 2 (Data, Var, Abs) */
0x0a, 0x21, 0x26, /* Usage 9761 (null) */
0x91, 0x02,       /* Output 2 (Data, Var, Abs) */
0x26, 0xff, 0x03, /* Logical Maximum 1023 */
0x46, 0xff, 0x03, /* Physical Maximum 1023 */
0x09, 0x2c,       /* Usage 44 (null) */
0x09, 0x2d,       /* Usage 45 (null) */
0x09, 0x2e,       /* Usage 46 (null) */
0x09, 0x2f,       /* Usage 47 (null) */
0x75, 0x10,       /* Report Size 16 */
0x95, 0x04,       /* Report Count 4 */
0x81, 0x02,       /* Input 2 (Data, Var, Abs) */

Por último, se definen 4 doble bytes, que son parecidos a los que usa el DualShock 3 para codificar los acelerómetros X, Y y Z, y el giroscopio.

0xc0      /* End_Collection */

Ese byte sólo cierra la colección y con ello el descriptor.

Este descriptor entonces codifica 13 botones, dos joysticks, presiones de los botones, acelerómetros, y giroscopio, utilizando 27 bytes: 2 bytes para los 13 botones (contando 3 bits de relleno), 1 byte para el hat switch, 4 bytes para los dos joysticks, 12 bytes para (supongo) la presión de 12 de los 13 botones, y 8 bytes para los acelerómetros y el giroscopio.

¿Cuál es entonces el dispositivo 12ba:0200, este famoso “gamepad”? Es este:

RockBand Stratocaster

RockBand Stratocaster

El bit 0 del primer byte es el botón azul, el bit 1 es el verde, el bit 2 es el rojo, etc. El primer joystick es ignorado; pero el segundo toma un eje para el whammy bar, y el otro para el “estilo del solo”… lo que sea que es eso.

La información básica del controlador (especialmente los primeros 7 bytes que envía el dispositivo) los encontré en esta página, pero casi todo lo demás lo tuve que averiguar (o adivinar) yo solo. Y no estoy 100% seguro de que tenga todo correctamente.

Sin embargo, lo que he sí tengo correcto sirvió perfectamente para que llevara a cabo mi proyecto… que esperaría que en este punto ya todo mundo pudiera adivinar cuál fue.

GIMX

(Esta entrada es la sexta parte de una serie que cubre un proyecto personal que realicé en el verano de 2014; pueden ver todas las partes aquí).

Originalmente iba a escribir acerca de una de las piezas del rompecabezas de mi proyecto que haría explícito exactamente a dónde iba con él. Decidí tomar una pequeña desviación hoy, sin embargo; porque si no mencionar a GIMX más tarde tal vez le quitaría méritos.

El proyecto GIMX (Game Input MultipleXer) obviamente está relacionado a mi proyecto, pero tiene suficientes diferencias como para que yo no considere al mío una modificación. El diseño del hardware (que lo pueden ver aquí) sí lo tomé completamente de ellos, porque yo no sé realmente de hardware. Es por eso que estudié Ciencias de la Computación y no Ingeniería en Computación; todo lo de los fierritos y alambritos me da una hueva mortal.

El software de GIMX al final sólo me sirvió de base para yo hacer lo mío; GIMX utiliza LUFA, que aunque se ve que tiene un montón de cosas bien chidas, al final yo decidí que me daba mucha hueva estudiar un marco de trabajo tan complejo para lo casi trivial que hago yo con mi Teensy++ 2.0. Aunque sí estudié algo del código de GIMX, terminé yo escribiendo mi propia versión del firmware para mi Teensy++ 2.0, basada en otro cuerpo de código (el ejemplo Raw HID de la página de Teensy).

De cualquier forma, enterarme de la existencia de GIMX fue lo que me hizo percatarme de que mi proyecto era posible, y nada más por el diseño de hardware y la guía de qué componentes comprar bastarían para que yo estuviera infinitamente agradecido con ellos.

La idea principal de GIMX es crear un firmware para el Teensy++ 2.0 (o cualquier tarjeta similar; al parecer hay chorrocientas parecidas) que emule a un controlador de PlayStation 3 (también de PlayStation 4 y de Xbox 360, pero esos no me importan). También la idea es emular un control a través de Bluetooth, pero eso no me servía de nada para mi proyecto, así que lo ignoré olímpicamente.

Como el Teensy++ 2.0 tiene el poder de cómputo equivalente a dos calculadoras más o menos, GIMX utiliza el CP2102 (o cualquier otro adaptador USB ↔ serial) para que una computadora le pueda mandar los “movimientos” del control al Teensy++ 2.0. El Teensy++ 2.0 y el CP2102 se conectan como se puede ver en el tutorial de GIMX.

La cosa se ve (esquemáticamente) así al final:

El diagrama

El diagrama

Yo uso exactamente la misma configuración de hardware; pero mi software es bastante distinto, incluyendo el firmware del Teensy++ 2.0.

La idea de GIMX es, básicamente, poder reemplazar un control del PlayStation 3 por el ratón y teclado de una computadora. En particular, esto permite jugar juegos FPS (first-person shooter) como dios manda, no con la desgracia que es el DualShock 3 (y me imagino el 4) cuando uno trata de destripar desconocidos en línea.

No me gustan muchas de las decisiones que tomaron los escritores de GIMX; su software toma control por completo de un teclado y ratón conectados a la computadora, y me parece que utilizan demasiado interfaces gráficas cuando un montón de cosas saldrían mejor con la línea de comandos o archivos de texto. En particular, que para configurar el mapeo de teclas del teclado a botones del DualShock 3 haya que usar una interfaz gráfica, o que tuviera que meterme al código de GIMX para ver qué opciones de línea de comandos toma el principal ejecutable, porque está pensado para ser ejecutado todo por ventanas, se me hacen ligeramente heréticas en Linux. Aunque claro, el software está pensado para gamers, no programadores.

Que tomen control absoluto del ratón tiene sentido; para que GIMX jala bien bien, uno necesita un ratón con una resolución altísima (recomiendan 5000 DPI), porque tienen que hacer transformaciones sobre cómo se está moviendo el ratón para que el Teensy++ 2.0 pueda emular movimientos fluidos y naturales de un DualShock 3. Que tomen control absoluto del teclado no tiene sentido, pero pues eso hacen.

Como sea; para mi proyecto no utilicé GIMX, pero sí me inspiré en ellos. De cualquier forma, GIMX me permitirá en un futuro tal vez comenzar a jugar juegos FPS en mi PlayStation 3 (o 4, cuando lo tenga); la verdad el DualShock 3 me pone de malas para FPSs, pero hay juegos FPS que sí me gustaría jugar en mi PS3 (BioShock y Fallout los más prominentes).

GIMX también resulta útil de vez en cuando en TPSs (third-person shooter, que son los que más juego), y de hecho ya lo usé para una cosilla. Sólo que para usarlo regularmente, sí voy a necesitar meterle mano al código, porque me parece terriblemente agresivo.

Así que para terminar esta parte; no utilicé el código de GIMX para mi proyecto, pero sí me inspiré en ellos, y su diseño de cómo acoplar el hardware sí se los fusilé por completo. Más adelante tal vez lo utilice para jugar FPSs, y de vez en cuando TPSs; pero sí cuestiono ciertas decisiones de diseño que tomaron al escribirlo.

De cualquier forma, GIMX es un proyecto súper chido porque me inspiró para yo poder hacer el mío.

Teensy++ 2.0

(Esta entrada es la quinta parte de una serie que cubre un proyecto personal que realicé en el verano de 2014; pueden ver todas las partes aquí).

La parte crucial de mi proyecto, sin la cual nada más podía llevarse a cabo, fue comprar un Teensy++ 2.0.

Teensy++ 2.0

Teensy++ 2.0

Esto sí no tengo ni la más remota idea de cómo conseguirlo en México. Debe ser posible, pero maldito si acaso sé dónde preguntar al menos.

El Teensy++ 2.0 es un sistema de desarrollo microcontrolador… definición que probablemente les sirva tanto a ustedes como inicialmente me sirvió a mí. Para la gente que sabe de desarrollo en hardware, lo más sencillo tal vez sea decirles que el Teensy++ 2.0 es básicamente un Arduino; puede utilizar las bibliotecas de Arduino, e incluso el ambiente de desarrollo, y es (hasta donde tengo entendido) casi 100% compatible con Arduino.

Para la gente (como yo) con antecedentes de programación, la mejor descripción del Teensy++ 2.0 es que es hardware “programable”. La chingaderita (mide como 5×2 centímetros) tiene un montón de compuertas lógicas; yo como programador escribo un programa en C, y una versión especial de gcc (el compilador normal de C de Linux) genera un ejecutable que puedo convertir a un binario, el cual puedo quemar (flash, le dicen) en el Teensy++ 2.0 como firmware.

En otras palabras, aunque Turing completo (la tarjeta es capaz de ejecutar cualquier programa, si tiene suficiente memoria), el Teensy++ 2.0 no es un CPU dado que no tiene realmente un conjunto de instrucciones… pero podría programarle un CPU con el conjunto de instrucciones que se me diera la gana, como hicimos en mi proyecto final de Arquitectura en la maestría. Aunque probablemente necesitaría más memoria.

Programar el Teensy++ 2.0 es deliciosamente restrictivo; olvídense de asignar memoria, mejor ni intentar utilizar recursión, y casi todas las variables más vale que sean globales y estáticas. También hace cosas raras para un programador como yo que casi nunca piensa en el hardware; por ejemplo, transmitir un bit por uno del montón de pines que tiene, se consigue asignándole 0 o 1 a una variable especial (por ejemplo, UEDATX).

Hay una comunidad bastante grande de gente que utiliza el Teensy++ 2.0 para cualquier cantidad de proyectos; échenles un ojo si quieren.

El hardware es realmente restrictivo; desde el punto de vista de software, no puede hacer mucho… y si somos justos, de hecho casi no puede hacer nada. ¿Para qué podría interesarme entonces a mí, que soy programador antes que nada? Pues obviamente me interesaba por lo que puede hacer desde el punto de vista de hardware.

¿Ven el conector mini USB que tiene el Teensy++ 2.0 en la imagen de arriba? Ahí se le conecta el cable USB con el cual quemo mis “programas” en la tarjeta. Pero no es para lo único que sirve.

El Teensy++ 2.0 tiene la capacidad de emular un dispositivo USB.

Y en particular, puede emular un dispositivo HID-USB.

Estrellita en la frente a mis lectores que puedan adivinar a dónde voy con todo esto.

El control DualShock 3

(Esta entrada es la cuarta parte de una serie que cubre un proyecto personal que realicé en el verano de 2014; pueden ver todas las partes aquí).

El control DualShock 3 es el control que, a partir de 2008, viene por omisión con el PlayStation 3, reemplazando (o mejor sería decirlo, extendiendo) al control SixAxis. Casi universalmente reconocido como uno de los mejores controles para videojuegos en existencia, ahora está siendo reemplazado por el DualShock 4, que es el que viene incluido con el PlayStation 4.

El DualShock 3 tiene muchas características que lo hacen interesante incluso fuera del ámbito de videojuegos; es un dispositivo Bluetooth y USB-HID casi estándar, lo que nos permite usarlo de forma casi inmediata en cualquier computadora moderna. El “casi” es porque la conectividad Bluetooth tiene un paso no estándar para autenticar al control con un PlayStation 3; y en el caso de USB-HID, existe al menos una función del dispositivo que yo no he logrado entender, pero que no es muy importante.

Lo de Bluetooth no me importa demasiado; que el dispositivo sea USB-HID en cambio lo hizo una de las piezas más en el rompecabezas de mi proyecto veraniego.

Continuando la introducción que dí de USB-HID, como el DualShock 3 es un dispositivo ídem, podemos analizar su reporte descriptivo en Linux utilizando lsusb. En esa entrada comentaba que para lograrlo hay que utilizar un truquito; la cosa es que si el dispositivo ya está controlado por el módulo usbhid del kernel (que ocurre de forma automática), el reporte descriptivo queda invisible, entonces hay que “liberar” al dispositivo del módulo. La explicación técnica se puede leer en este artículo de LWN.net, pero la manera rápida y sucia de hacerlo es haciendo

echo -n '3-1:1.0' > /sys/bus/usb/drivers/usbhid/unbind

en una terminal como superusuario; por supuesto, 3-1:1.0 es la dirección física donde conecté el DualShock 3 en mi computadora, tienen que usar la correcta en la suya.

Como sea, hecho el truco, el reporte descriptivo del DualShock 3 es este:

Report Descriptor: (length is 148)
  Item(Global): Usage Page, data= [ 0x01 ] 1
                  Generic Desktop Controls
  Item(Local ): Usage, data= [ 0x04 ] 4
                  Joystick
  Item(Main  ): Collection, data= [ 0x01 ] 1
                  Application
  Item(Main  ): Collection, data= [ 0x02 ] 2
                  Logical
  Item(Global): Report ID, data= [ 0x01 ] 1
  Item(Global): Report Size, data= [ 0x08 ] 8
  Item(Global): Report Count, data= [ 0x01 ] 1
  Item(Global): Logical Minimum, data= [ 0x00 ] 0
  Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
  Item(Main  ): Input, data= [ 0x03 ] 3
                  Constant Variable Absolute No_Wrap Linear
                  Preferred_State No_Null_Position Non_Volatile Bitfield
  Item(Global): Report Size, data= [ 0x01 ] 1
  Item(Global): Report Count, data= [ 0x13 ] 19
  Item(Global): Logical Minimum, data= [ 0x00 ] 0
  Item(Global): Logical Maximum, data= [ 0x01 ] 1
  Item(Global): Physical Minimum, data= [ 0x00 ] 0
  Item(Global): Physical Maximum, data= [ 0x01 ] 1
  Item(Global): Usage Page, data= [ 0x09 ] 9
                  Buttons
  Item(Local ): Usage Minimum, data= [ 0x01 ] 1
                  Button 1 (Primary)
  Item(Local ): Usage Maximum, data= [ 0x13 ] 19
                  (null)
  Item(Main  ): Input, data= [ 0x02 ] 2
                  Data Variable Absolute No_Wrap Linear
                  Preferred_State No_Null_Position Non_Volatile Bitfield
  Item(Global): Report Size, data= [ 0x01 ] 1
  Item(Global): Report Count, data= [ 0x0d ] 13
  Item(Global): Usage Page, data= [ 0x00 0xff ] 65280
                  (null)
  Item(Main  ): Input, data= [ 0x03 ] 3
                  Constant Variable Absolute No_Wrap Linear
                  Preferred_State No_Null_Position Non_Volatile Bitfield
  Item(Global): Logical Minimum, data= [ 0x00 ] 0
  Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
  Item(Global): Usage Page, data= [ 0x01 ] 1
                  Generic Desktop Controls
  Item(Local ): Usage, data= [ 0x01 ] 1
                  Pointer
  Item(Main  ): Collection, data= [ 0x00 ] 0
                  Physical
  Item(Global): Report Size, data= [ 0x08 ] 8
  Item(Global): Report Count, data= [ 0x04 ] 4
  Item(Global): Physical Minimum, data= [ 0x00 ] 0
  Item(Global): Physical Maximum, data= [ 0xff 0x00 ] 255
  Item(Local ): Usage, data= [ 0x30 ] 48
                  Direction-X
  Item(Local ): Usage, data= [ 0x31 ] 49
                  Direction-Y
  Item(Local ): Usage, data= [ 0x32 ] 50
                  Direction-Z
  Item(Local ): Usage, data= [ 0x35 ] 53
                  Rotate-Z
  Item(Main  ): Input, data= [ 0x02 ] 2
                  Data Variable Absolute No_Wrap Linear
                  Preferred_State No_Null_Position Non_Volatile Bitfield
  Item(Main  ): End Collection, data=none
  Item(Global): Usage Page, data= [ 0x01 ] 1
                  Generic Desktop Controls
  Item(Global): Report Size, data= [ 0x08 ] 8
  Item(Global): Report Count, data= [ 0x27 ] 39
  Item(Local ): Usage, data= [ 0x01 ] 1
                  Pointer
  Item(Main  ): Input, data= [ 0x02 ] 2
                  Data Variable Absolute No_Wrap Linear
                  Preferred_State No_Null_Position Non_Volatile Bitfield
  Item(Global): Report Size, data= [ 0x08 ] 8
  Item(Global): Report Count, data= [ 0x30 ] 48
  Item(Local ): Usage, data= [ 0x01 ] 1
                  Pointer
  Item(Main  ): Output, data= [ 0x02 ] 2
                  Data Variable Absolute No_Wrap Linear
                  Preferred_State No_Null_Position Non_Volatile Bitfield
  Item(Global): Report Size, data= [ 0x08 ] 8
  Item(Global): Report Count, data= [ 0x30 ] 48
  Item(Local ): Usage, data= [ 0x01 ] 1
                  Pointer
  Item(Main  ): Feature, data= [ 0x02 ] 2
                  Data Variable Absolute No_Wrap Linear
                  Preferred_State No_Null_Position Non_Volatile Bitfield
  Item(Main  ): End Collection, data=none
  Item(Main  ): Collection, data= [ 0x02 ] 2
                  Logical
  Item(Global): Report ID, data= [ 0x02 ] 2
  Item(Global): Report Size, data= [ 0x08 ] 8
  Item(Global): Report Count, data= [ 0x30 ] 48
  Item(Local ): Usage, data= [ 0x01 ] 1
                  Pointer
  Item(Main  ): Feature, data= [ 0x02 ] 2
                  Data Variable Absolute No_Wrap Linear
                  Preferred_State No_Null_Position Non_Volatile Bitfield
  Item(Main  ): End Collection, data=none
  Item(Main  ): Collection, data= [ 0x02 ] 2
                  Logical
  Item(Global): Report ID, data= [ 0xee ] 238
  Item(Global): Report Size, data= [ 0x08 ] 8
  Item(Global): Report Count, data= [ 0x30 ] 48
  Item(Local ): Usage, data= [ 0x01 ] 1
                  Pointer
  Item(Main  ): Feature, data= [ 0x02 ] 2
                  Data Variable Absolute No_Wrap Linear
                  Preferred_State No_Null_Position Non_Volatile Bitfield
  Item(Main  ): End Collection, data=none
  Item(Main  ): Collection, data= [ 0x02 ] 2
                  Logical
  Item(Global): Report ID, data= [ 0xef ] 239
  Item(Global): Report Size, data= [ 0x08 ] 8
  Item(Global): Report Count, data= [ 0x30 ] 48
  Item(Local ): Usage, data= [ 0x01 ] 1
                  Pointer
  Item(Main  ): Feature, data= [ 0x02 ] 2
                  Data Variable Absolute No_Wrap Linear
                  Preferred_State No_Null_Position Non_Volatile Bitfield
  Item(Main  ): End Collection, data=none
  Item(Main  ): End Collection, data=none

Eso se ve medianamente intimidante; pero después de perder el tiempo con el estándar USB-HID, de hecho se vuelve legible. Voy a comentar algunas partes del reporte descriptivo, pero siguiendo su notación en bytes con comentarios en C:

0x05, 0x01,        /* Usage Page (Generic Desktop Ctrls) */
0x09, 0x04,        /*/ Usage (Joystick) */
0xA1, 0x01,        /* Collection (Physical) */

Este es sencillamente el encabezado donde especifica que lo que sigue es la descripción del un joystick.

0xA1, 0x02,        /*   Collection (Application) */
0x85, 0x01,        /*     Report ID (1) */
0x75, 0x08,        /*     Report Size (8) */
0x95, 0x01,        /*     Report Count (1) */
0x15, 0x00,        /*     Logical Minimum (0) */
0x26, 0xFF, 0x00,  /*     Logical Maximum (255) */
0x81, 0x03,        /*     Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */

El DualSock 3 reserva el primer byte; no tengo idea para qué, pero al menos en mis controles nunca envía nada distinto a 0x00.

0x75, 0x01,        /*     Report Size (1) */
0x95, 0x13,        /*     Report Count (19) */
0x15, 0x00,        /*     Logical Minimum (0) */
0x25, 0x01,        /*     Logical Maximum (1) */
0x35, 0x00,        /*     Physical Minimum (0) */
0x45, 0x01,        /*     Physical Maximum (1) */
0x05, 0x09,        /*     Usage Page (Button) */
0x19, 0x01,        /*     Usage Minimum (0x01) */
0x29, 0x13,        /*     Usage Maximum (0x13) */
0x81, 0x02,        /*     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */
0x75, 0x01,        /*     Report Size (1) */
0x95, 0x0D,        /*     Report Count (13) */
0x06, 0x00, 0xFF,  /*     Usage Page (Vendor Defined 0xFF00) */
0x81, 0x03,        /*     Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */

4 bytes, o un entero de 32 bits (es exactamente lo mismo), para representar potencialmente 32 botones; actualmente sólo 19 de hecho son considerados (bits 0-18), y 13 reservados para uso futuro (bits 19-31).

0x15, 0x00,        /*     Logical Minimum (0) */
0x26, 0xFF, 0x00,  /*     Logical Maximum (255) */
0x05, 0x01,        /*     Usage Page (Generic Desktop Ctrls) */
0x09, 0x01,        /*     Usage (Pointer) */
0xA1, 0x00,        /*     Collection (Undefined) */
0x75, 0x08,        /*       Report Size (8) */
0x95, 0x04,        /*       Report Count (4) */
0x35, 0x00,        /*       Physical Minimum (0) */
0x46, 0xFF, 0x00,  /*       Physical Maximum (255) */
0x09, 0x30,        /*       Usage (X) */
0x09, 0x31,        /*       Usage (Y) */
0x09, 0x32,        /*       Usage (Z) */
0x09, 0x35,        /*       Usage (Rz) */
0x81, 0x02,        /*       Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */
0xC0,              /*     End Collection */

Cuatro ejes: X, Y, Z, y rotación de Z. Cada eje es un byte (“Report Size (8)”), y se toma el valor del byte menos 127 como el valor del eje; en otras palabras, 0 – 127 = -127 es el eje en su posición más baja (o a la izquierda), 127 – 127 = 0 es el eje en “reposo” (centrado), y 255 – 127 = 128 es el eje en su posición más alta (o a la derecha). El DualShock 3 utiliza el estándar HID para definir eje Z y rotación de Z, pero esos dos realmente son los ejes del joystick derecho, y los dos primeros ejes son los del joystick izquierdo.

0x05, 0x01,        /*     Usage Page (Generic Desktop Ctrls) */
0x75, 0x08,        /*     Report Size (8) */
0x95, 0x27,        /*     Report Count (39) */
0x09, 0x01,        /*     Usage (Pointer) */
0x81, 0x02,        /*     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */
0x75, 0x08,        /*     Report Size (8) */
0x95, 0x30,        /*     Report Count (48) */
0x09, 0x01,        /*     Usage (Pointer) */
0x91, 0x02,        /*     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) */
0x75, 0x08,        /*     Report Size (8) */
0x95, 0x30,        /*     Report Count (48) */
0x09, 0x01,        /*     Usage (Pointer) */
0xB1, 0x02,        /*     Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) */
0xC0,              /*   End Collection */
0xA1, 0x02,        /*   Collection (Application) */
0x85, 0x02,        /*     Report ID (2) */
0x75, 0x08,        /*     Report Size (8) */
0x95, 0x30,        /*     Report Count (48) */
0x09, 0x01,        /*     Usage (Pointer) */
0xB1, 0x02,        /*     Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) */
0xC0,              /*   End Collection */
0xA1, 0x02,        /*   Collection (Application) */
0x85, 0xEE,        /*     Report ID (238) */
0x75, 0x08,        /*     Report Size (8) */
0x95, 0x30,        /*     Report Count (48) */
0x09, 0x01,        /*     Usage (Pointer) */
0xB1, 0x02,        /*     Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) */
0xC0,              /*   End Collection */
0xA1, 0x02,        /*   Collection (Application) */
0x85, 0xEF,        /*     Report ID (239) */
0x75, 0x08,        /*     Report Size (8) */
0x95, 0x30,        /*     Report Count (48) */
0x09, 0x01,        /*     Usage (Pointer) */
0xB1, 0x02,        /*     Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) */
0xC0,              /*   End Collection */
0xC0,              /* End Collection */

Esta última parte explica cómo el reporte entero del dispositivo utiliza 48 bytes (más uno de identificador): 3 colecciones de 8 bytes de “controles de escritorio genérico” (“Generic Desktop Ctrls”), y tres colecciones de 8 bytes de aplicación.

El byte 0 sirve de identificador y (hasta donde he visto) es siempre 1.

El byte 1 (como dije arriba) está reservado; los siguientes 4 bytes (bytes 2-5) tienen información de los botones: bit prendido siginifica que el botón está presionado; apagado implica no presionado. Aunque son 32 bits en total (4 bytes), realmente sólo se usan 19, con 13 reservados.

Los bytes 6, 7, 8 y 9 son los ejes de los joysticks, y a partir del 10 (realmente 13; 10, 11 y 12 sólo me reportan ceros) hasta el 24 son las presiones de cada botón: 0 es el botón casi no está presionado, 255 es el botón está completamente presionado. Esas son las 3 colecciones de escritorio genérico (24 bytes). Los otros 24 bytes contienen las colecciones de aplicación, pero hasta donde entiendo sólo se usan los bytes 41-48; 2 bytes para el acelerómetro X, 2 bytes para el acelerómetro Y, 2 bytes para el acelerómetro Z, y dos bytes para el giroscopio. No tengo idea qué carajo hagan los bytes 25-40, pero en uno de mis controles siempre reporta:

00 00 00 00 03 ef 16 00 00 00 00 33 ae 77 00 40

A lo mejor ahí estarán las presiones de los 13 botones reservados. Por cierto, todo esto es como yo lo he entendido; si estoy entendiendo algo mal, por favor díganme.

¿Para qué sirve todo esto? Para mi proyecto de verano, no me sirvió de mucho; pero sí será de utilidad (de hecho ya lo fue) para otro miniproyecto relacionado. Con la información de arriba puedo leer bit a bit la información que un DualShock 3 envía; pero esto, además de que hay formas mucho más sencillas de hacerlo (por no decir de mucho más alto nivel), cualquier güey lo hace. Yo quería hacer algo más interesante (y lo hice).

En la siguiente parte de esta serie explicaré la pieza principal del rompecabezas. No sé si con ello ya quede claro mi proyecto; pero si no, la entrada que le seguirá debería dejarlo claro.

CP2102 UART Bridge

(Esta entrada es la tercera parte de una serie que cubre un proyecto personal que realicé en el verano de 2014; pueden ver todas las partes aquí).

La familia de adaptadores USB ↔ serial cp210x es bastante común para la gente que quiere comunicarse con hardware de forma serial. Hace unos años (tal vez habría que decir décadas) no era necesario utilizar este tipo de adaptadores: todas las computadoras tenían al menos un conector de este estilo incluido, llamado (imaginativamente) el puerto serial (serial port); en el mundo de DOS se le conocía como COM1 (y si había más eran COM2, etc.), y yo todavía llegué a conectar algún módem de esta manera a una computadora.

Como decía en la entrada anterior de esta serie, el estándar USB ha reemplazado a casi todos los conectores de la antigüedad; los conectores PS1, paralelo, y serial incluidos. Por lo tanto, si alguien quiere (como yo quería) comunicarse de forma serial usando una computadora moderna, uno necesita un adaptador USB ↔ serial.

Leyendo en Internet, rápidamente me decidí por un CP2102 UART Bridge, que encontré a precio de ganga en Amazon; el que ligo cuesta casi 7 dólares, pero el que de hecho compré me salió en menos de 3, con envío incluido. Lamentablemente, no sé dónde conseguir este tipo de cosas en México, así que lo pedí por Amazon y lo envié a la dirección de Omar en Boston (Amazon generalmente no envía electrónicos fuera del gabacho), donde lo recogí cuando pasé a verlo a finales de mayo.

CP2102

CP2102

(Siendo 100% honesto, sí lo encontré en MercadoLibre; pero el precio era más del doble).

Me decidí por el CP2102 básicamente porque es muy barato, y porque está muy bien soportado en Linux (niguna de esas razones me sorprende; no hace nada terriblemente interesante). En Gentoo, sencillamente tuve que habilitar el módulo del kernel cp210x (funciona para el CP2101 y el CP2103 también), con la opción USB_SERIAL_CP210X, y después sencillamente lo conecté a mi computadora. Inmediatamente el adaptador queda registrado con el dispositivo /dev/ttyUSB0, y uno puede comenzar a usarlo sin problemas.

Bueno, casi; el dispositivo por omisión tiene permisos de escritura únicamente para root, así que usando la siguiente regla de udev:

SUBSYSTEMS=="usb", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", MODE:="0666"
KERNEL=="ttyUSB*", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", MODE:="0666"

puedo darle permisos a todo mundo de forma automática, lo que me permite que mis aplicaciones corran para cualquier usuario.

Como sea; el CP2102 es bastante sencillo de usar (si es que acaso el hardware está bien conectado), uno únicamente abre el dispositivo, y puede empezar a escribir y leer información de él sin muchos problemas:

#include <termios .h>
#include <unistd .h>
#include <errno .h>
#include <fcntl .h>
#include <stdio .h>
#include <stdlib .h>
#include <stdint .h>

#define TTY_BAUDRATE   B38400

int
main(int argc, char* argv[])
{
        struct termios options;
        int fd;

        if ((fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NONBLOCK)) < 0) {
                return -1;
        } else {
                tcgetattr(fd, &options);
                cfsetispeed(&options, TTY_BAUDRATE);
                cfsetospeed(&options, TTY_BAUDRATE);
                cfmakeraw(&options);
                if (tcsetattr(fd, TCSANOW, &options) < 0) {
                        close(fd);
                        return -1;
                }
                tcflush(fd, TCIFLUSH);
        }

        uint8_t byte = 0xff;

        /* Envía byte. */
        int r = write(fd, &byte, 1);
        tcflush(fd, TCIFLUSH);

        /* Recibe byte. */
        fd_set readfds;

        struct timeval timeout = {
                .tv_sec = 1,
                .tv_usec = 0
        };

        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);

        int status = select(fd+1, &readfds, NULL, NULL, &timeout);
        if (!status)
                return -1;
        
        if (FD_ISSET(fd, &readfds)) {
                r = read(fd, &byte, 1);
        } else if(status == EINTR) {
                return -1;
        }

        return (int)byte;
}

Obviamente se pueden transmitir secuencias de bytes más largas que 1, pero para lo que quería hacer 1 bastaba… o al menos no he necesitado más.

Por supuesto, el chiste de todo esto es, ¿a dónde van esos bytes que estoy enviando? El adaptador CP2120 únicamente me permite comunicarme de forma serial vía USB; es sólo una pieza más del rompecabezas que estuve armando para mi proyecto. Y, en retrospectiva, de hecho fue la pieza más sencilla; casi no tuve que hacer nada para que funcionara.

Del resto de las piezas escribiré más adelante.

HID class

(Esta entrada es la segunda parte de una serie que cubre un proyecto personal que realicé en el verano de 2014; pueden ver todas las partes aquí).

USB ha tenido a bien reemplazar casi todos los conectores de una computadora; en el caso de los teléfonos celulares inteligentes modernos (que son realmente computadoras), de hecho reemplaza todos los conectores, excepto el conector para audífonos. Bueno; hablo de teléfonos celulares civilizados: obviamente el iPhone tiene que tener su conector propietario, porque si no cómo le van a cobrar millones de dólares a sus usuarios cuando quieran reemplazarlo.

El bus serial universal (que es lo que USB significa) ha tenido este éxito por muchos motivos, de los cuales no voy a mencionar ni uno solo. Yo de lo que quiero escribir hoy aquí es de la clase USB para dispositivos con interfaz humana, o USB human interface device class en inglés.

Un dispositivo de clase USB-HID tiene la capacidad de decirle a la computadora a la cual se conecta ciertos datos; si es un teclado, por ejemplo, cuántas teclas tiene; si es un ratón, que resolución tiene el sistema de coordenadas que puede manejar; si es un joystick o gamepad, cuántos ejes y botones tiene, etc. Como USB-HID es un estándar abierto, en general esto permite que no haya necesidad de escribir controladores (drivers) para estos dispositivos: todos los sistemas operativos en existencia los soportan de manera nativa. Uno sólo conecta el dispositivo USB-HID a la computadora, y el mismo dispositivo le dice qué es, y qué características tiene. La computadora puede entonces comenzar a usar el dispositivo de inmediato, porque éste ya le dijo qué le puede pedir.

Voy a hacer la historia corta: le pide bytes. Lo único que debe hacer el dispositivo es explicarle a la computadora qué significa el bit número X del byte Y en el reporte que cada determinado número de milisegundos la computadora le pide. Es a la vez primitivo y elegante el asunto.

Cuando el dispositivo USB-HID se conecta a la computadora, ésta le pide un descriptor de dispositivo (device descriptor), el cual tiene información como qué protocolo utiliza, cuántas configuraciones maneja, y lo más visible para todo mundo, dos llaves de diccionario para el identificador de fabricante y el identificador de producto. Estas dos llaves son en Linux muy fáciles de revisar (me imagino que en otros sistemas operativos también; pero como no los uso, no tengo idea); usando el comando lsusb en mi laptop produce el siguiente resultado:

canek@acero ~ $ lsusb
Bus 004 Device 004: ID 8086:0189 Intel Corp. 
Bus 004 Device 003: ID 1bcf:288e Sunplus Innovation Technology Inc. 
Bus 004 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 004 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 003 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 002 Device 031: ID 18d1:4ee1 Google Inc. Nexus 4 / 10
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 008: ID 0461:0010 Primax Electronics, Ltd HP Keyboard
Bus 001 Device 009: ID 0461:4d0f Primax Electronics, Ltd HP Optical Mouse
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Las primeras columnas vienen de Linux; especifican el puerto USB físico donde están conectados los dispositivos. La últimas columnas, que son humanamente legibles ("Primax Electronics, Ltd HP Keyboard") son entradas de un diccionario que Linux y todos los sistemas operativos modernos mantienen; es una base de datos que especifica qué información corresponde a las llaves que se muestran en la columna central ("0461:0010"), y que de hecho es la única información que viene del dispositivo USB. El primer número hexadecimal (0x0461) es el identificador de fabricante (VendorId), y el segundo (0x0010) es el identificador de producto (ProductId).

Además del identificador de dispositivo, un dispositivo USB-HID le envía a la computadora un reporte descriptivo (report descriptor) que es donde se (valga la rebuznancia) describe lo que el dispositivo puede o no puede hacer. Usando algunas opciones extras de lsusb, podemos ver esta información:

canek@acero ~ $ lsusb -vvv -d 0461:0010
...
          Report Descriptor: (length is 65)
            Item(Global): Usage Page, data= [ 0x01 ] 1
                            Generic Desktop Controls
            Item(Local ): Usage, data= [ 0x06 ] 6
                            Keyboard
            Item(Main  ): Collection, data= [ 0x01 ] 1
                            Application
            Item(Global): Usage Page, data= [ 0x07 ] 7
                            Keyboard
            Item(Local ): Usage Minimum, data= [ 0xe0 ] 224
                            Control Left
            Item(Local ): Usage Maximum, data= [ 0xe7 ] 231
                            GUI Right
            Item(Global): Logical Minimum, data= [ 0x00 ] 0
            Item(Global): Logical Maximum, data= [ 0x01 ] 1
            Item(Global): Report Size, data= [ 0x01 ] 1
            Item(Global): Report Count, data= [ 0x08 ] 8
            Item(Main  ): Input, data= [ 0x02 ] 2
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Global): Report Count, data= [ 0x01 ] 1
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Main  ): Input, data= [ 0x01 ] 1
                            Constant Array Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Global): Report Count, data= [ 0x03 ] 3
            Item(Global): Report Size, data= [ 0x01 ] 1
            Item(Global): Usage Page, data= [ 0x08 ] 8
                            LEDs
            Item(Local ): Usage Minimum, data= [ 0x01 ] 1
                            NumLock
            Item(Local ): Usage Maximum, data= [ 0x03 ] 3
                            Scroll Lock
            Item(Main  ): Output, data= [ 0x02 ] 2
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Global): Report Count, data= [ 0x05 ] 5
            Item(Global): Report Size, data= [ 0x01 ] 1
            Item(Main  ): Output, data= [ 0x01 ] 1
                            Constant Array Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Global): Report Count, data= [ 0x06 ] 6
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Global): Logical Minimum, data= [ 0x00 ] 0
            Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
            Item(Global): Usage Page, data= [ 0x07 ] 7
                            Keyboard
            Item(Local ): Usage Minimum, data= [ 0x00 ] 0
                            No Event
            Item(Local ): Usage Maximum, data= [ 0xff 0x00 ] 255
                            (null)
            Item(Main  ): Input, data= [ 0x00 ] 0
                            Data Array Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Main  ): End Collection, data=none
...

(Estoy aquí usando un truco en el que no quiero entrar en detalle; pero básicamente así podemos obtener el reporte descriptivo).

Esa información que está allá arriba es bastante legible para un humano; de nuevo, Linux es el que está haciendo esa chamba por nosotros. El dispositivo lo que envía son una serie de bytes codificados para expresar lo de arriba; por ejemplo, el primer elemento especifica un “Usage Page: Generic Desktop”: esto está codificado por los bytes 0x05 (Usage Page), y 0x01 (Generic Desktop). El segundo elemento es un “Usage: Keyboard”, que está codificado por los bytes 0x09 (Usage) y 0x06 (Keyboard). En todos los tutoriales en línea esto se suele representar así (usando C, pero cualquier lenguaje funciona):

0x05, 0x01, /* Usage Page (Generic Desktop) */
0x09, 0x06, /* Usage (Keyboard) */
...

Pero obviamente lo que la computadora recibe es 0x05 0x01 0x09 0x06 ...

Con esta información, la computadora sabe cuántos bytes pedirle a un dispositivo USB-HID, y qué codifica cada uno de estos bytes; el estándar es (paradójicamente) muy flexible e increíblemente rígido al respecto. Muy flexible porque uno puede enviar vía USB básicamente lo que a uno se le dé la regalada gana; pero los reportes descriptivos sólo codifican información para un puñado de cosas: ejes, botones/teclas apretados o no apretados, y cosas de este estilo. Sin embargo, como todo esto son únicamente bytes, uno puede darle la vuelta sencillamente describiéndolo como un “eje”, cuando en realidad está enviando un número que representa la temperatura en grados centígrados. O lo que sea; la verdad no leí el estándar completo (lo pueden bajar aquí), sólo lo hojeé hasta que me salí con la mía.

Y con la mía me salí; aunque no al 100%: aún hay un detalle que necesito resolver, pero es secundario a mis principales objetivos, y lo que quería hacer ya lo hice casi absolutamente todo. Pero si quisiera resolver absolutamente todo, debería leer con cuidado el estándar y ver si le puedo dar la vuelta al último obstáculo que me he encontrado.

De eso escribiré más adelante.

El formato MIDI

(Esta entrada es la primera parte de una serie que cubre un proyecto personal que realicé en el verano de 2014; pueden ver todas las partes aquí).

Clásico: digo que no debería dejar de escribir, y prontamente decido dejar de escribir durante tres semanas. El motivo por el cual no he escrito estas semanas, es porque durante estas vacaciones (además de meditar y tomar decisiones Muy Importantes™) me he estado divirtiendo como pocas veces en mi vida, trabajando en un proyecto personal que está básicamente terminado, ya que sólo falta una pequeña pieza del rompecabezas.

Esta entrada es acerca de otra de las piezas: el formato MIDI.

Durante mis dos últimos años en el CCH Sur, llevé a la escuela una guitarra todos los días. El resultado de esto fue que puedo tocar (mal) un puñado de canciones, y algunas de ellas hasta las puedo cantar (peor todavía). Tomé un cursito de guitarra bastante malo, que además no terminé, y el resto de mi educación musical fue juntarme con los chavos que tocaban la guitarra y tratar de robarles los acordes y/o requintos.

Además de eso, y al igual que (me imagino) casi todos los estudiantes de secundaria pública en el país, tuve una flauta dulce donde alguna vez (recuerdo vagamente) llegué a tocar la Oda a la Alegría de Beethoven.

Y ya: esa es toda la “educación” musical que he tenido; no sé nada de teoría musical, soy incapaz de leer una partitura, y mientras que creo que soy capaz de dibujar o esculpir algo que al menos se parezca un poco a lo que tuviera en mi mente antes de empezar, me sentiría completamente inhabilitado para poder reproducir en ningún instrumento una tonadita que yo me inventara… y de hecho no estoy seguro de poder inventarme una tonadita.

Todo lo anterior es para explicar por qué yo, asiduo como soy a casi todo relacionado con la computadora, jamás utilicé ni me interesó mucho el formato MIDI.

Para finales de los setentas del siglo pasado la música electrónica había evolucionado de ser un curioso experimento a ser parte fundamental del acto de varios artistas, y los instrumentos electrónicos tenían la enorme ventaja de poder guardar y reproducir las actuaciones de los artistas que los utilizaban, usando una fracción minúscula del ancho de banda que usan los instrumentos analógicos.

Me explico: si yo con mi guitarra de Paracho, Michoacán, quiero grabar Wendolyne, no tengo de otra sino poner un micrófono enfrente de la misma y grabar las vibraciones del aire en formato WAVE, que es del orden (más o menos) de diez megabytes por cada minuto de audio. Claro, ahora en el siglo XXI podemos utilizar MP3, que mejora en un orden de magnitud las cosas a (más o menos) un megabyte por minuto; pero las computadoras personales de finales de los setentas no tenían suficiente procesador para poder reproducir MP3 (básicamente no existían, además), a nadie en su sano juicio se le había ocurrido inventar MP3, e incluso si hubiera habido suficiente procesador, un megabyte por minuto era una fortuna en ese entonces.

Los instrumentos electrónicos pueden superar esto por mucho, porque en lugar de guardar vibraciones del aire, sencillamente pueden guardar la información musical: en el segundo 0, se tocó la nota Do a tal volumen y velocidad; en el segundo 0.024 se tocó la nota Mi a tal volumen y velocidad; en el segundo 0.57 se tocó la nota Ra a tal volumen y velocidad (si no entienden el chiste yo no se los voy a explicar). En casi todos los instrumentos electrónicos, las notas son sencillamente cerrar un circuito, así que guardando el tiempo en que el circuito se cierra y cuando se abre de nuevo, uno puede guardar casi perfectamente la información interesante de un acto.

A finales de los setentas casi todos los fabricantes de instrumentos electrónicos tenían formatos propietarios, que hacía que los músicos se jalaran las greñas porque era muy común que les gustara usar el teclado electrónico de un fabricante, y la batería electrónica de otro; lo que ocasionaba que combinar las distintas grabaciones fuera un infierno porque utilizaban formatos distintos.

Para inicios de los ochentas los fabricantes decidieron ponerse de acuerdo, y se creó el formato MIDI, que especifica en doloroso detalle no sólo el formato digital (unos y ceros) de MIDI, sino también cosas como conectores, voltajes, y otras cosas que únicamente a músicos podrían interesarles.

A mí no me importa nada fuera de los unos y los ceros; conectores, voltajes, y cosas de músicos me tienen sin cuidado. El formato de los archivos MIDI (generalmente con extensión .mid) es lo que tuve que estudiar y programar.

Un archivo MIDI tiene un simple encabezado de 12 bytes donde se especifica el número de pistas que tendrá; es común (pero no obligatorio) que cada pista represente las notas de un instrumento distinto. Cada una de las pistas consiste en “eventos”, donde se especifica el tiempo del evento, el evento mismo, y uno o dos parámetros del mismo. En el caso de las notas, los eventos son generalmente “nota prendida” y “nota apagada”, un canal (cada pista puede tener hasta 16 canales), el número de la nota, y la duración (o velocidad) de la misma. Estos son los eventos que a mí me interesaban; y más aún, me interesaban los eventos de un único instrumento.

Todo el formato MIDI está pensado para poder utilizar el mínimo número de bits posible; y lo consigue de forma magistral: todo el Carmina Burana debe utilizar menos de un megabyte de memoria, incluyendo todos los instrumentos de la orquesta. El precio que se paga es que esta información es inútil si uno no tiene lo necesario para reproducirlo; para reproducir un archivo MIDI propiamente, uno necesita “fuentes de sonido” (sound fonts), que es básicamente los sonidos de las notas de todos los posibles instrumentos que el archivo MIDI necesita. Sin una buena fuente de sonido, cualquier archivo MIDI suena básicamente como la musiquita de Super Mario Bros.

Cuando comencé a usar la computadora a inicios de los noventas, y cuando compré mi primea SoundBlaster, todavía llegué a toparme con archivos MIDI; pero justamente como nunca me molesté en buscar fuentes de sonido, nunca le vi mucho sentido, porque todo sonaba como la musiquita de Super Mario Bros. Con una buena fuente de sonido, hacer música con MIDI debe ser bastante chido, y de hecho casi todos los músicos profesionales en la actualidad lo utilizan de alguna u otra forma.

Como sea, y volviendo al formato de los archivos MIDI, cuando digo que los eventos tienen un “tiempo”, este tiempo no está representado en segundos, ni milisegundos, ni microsegundos. De hecho, olvídense de segundos; el tiempo está representado en… ¿saben qué? Aún ahora no sé en qué chingados está representado el tiempo; tiene que ver con pulsaciones por segundo, pulsos por cuartos de nota, submarcos, pulsos por minutos, y no sé qué madres más. No me interesa en lo más mínimo; pero lo necesitaba porque necesitaba el tiempo preciso en que cada nota se prendía y se apagaba. Y para acabarla de amolar, el tiempo no es absoluto; es relativo a la nota anterior: es un formato acumulativo, donde el tiempo de cada nota es una delta que se le suma al tiempo de la nota anterior.

Después de muchos quebraderos de cabeza, conseguí la fórmula que me permitía convertir el tiempo de cada nota (después de obtenerlo a partir del tiempo de la nota anterior y de la delta) a milisegundos, y me puse a sacar los tiempos de las notas del instrumento (o sea la pista) que me interesaba. Y por supuesto todo se desincronizaba; pero esto ocurría únicamente de vez en cuando, y únicamente en algunas canciones.

Estuve días golpeándome la cabeza contra un muro hasta que por fin encontré el problema: estaba calculando el tiempo utilizando las notas de la pista que me interesaba; y hay que usar todas las pistas. En otras palabras, si hay una pausa en las notas de la guitarra, pero en esa pausa la batería sí reproduce notas, la siguiente delta de la guitarra no se aplica a la última nota de la guitarra, sino a la última nota de cualquier instrumento (en este ejemplo, la batería). Lo cual tiene sentido cuando uno ve lo ridículamente pequeño que es un archivo MIDI; no hay problema en preprocesarlo todo de antemano para poder tener la información de todas las pistas disponible.

Y aún así, todavía tengo unas cuantas canciones donde de cualquier forma se me desincronizan las cosas. No tengo idea de qué pueda estar pasando; como el formato MIDI acepta cualquier cantidad de madres (por ejemplo, las letras de las canciones pueden incluirse en el archivo, para hacer cosas como karaokes), no sé si a algunas de ellas les esté tomando en cuenta el tiempo cuando no debería, o qué carajo: pero como sólo ocurre con dos o tres canciones, decidí esas arreglarlas a pie, y olvidarme del asunto para siempre. Con lo que tenía era más que suficiente para hacer lo que quería hacer, y de hecho ya lo hice; supongo que sí descubriera cuál era el problema estaría chido, pero a estas alturas ya es un extra. Lo que quería conseguir ya lo conseguí.

Para conseguirlo, escribí un programa que convertía la información de un archivo MIDI a un formato que me inventé donde dice en que nanosegundo ocurre que se prende o apaga una nota; primero lo hice en Python, pero he estado convirtiendo todo a Vala, porque es 10 veces más rapido, aunque como los archivos son todos chiquititos realemente no sería tan grave dejarlo en Python. También utilicé un programa que convertía el MIDI a un formato CSV (tipo hoja de cálculo), pero como no me salían las cosas terminé escribiendo yo uno igual, porque no me quedaba claro si había un error en el programa o cómo interpretaba yo las cosas (el error era mío, pero pues ya tengo mi programa que lee MIDIs directamente).

El medio entender el formato MIDI fue sólo una de las partes del proyecto en el que estuve trabajando; tengo todavía la duda de porqué un par de canciones se me desincronizan, pero fuera de eso creo que tengo dominada esta parte. Y de hecho, medio entender el formato MIDI me resultó de utilidad en otra de las partes del proyecto que encontré más adelante; pero de eso escribiré luego.

Gimme power

Mi teléfono celular tiene el mismo problema que tienen muchos dispositivos modernos: la batería le dura alrededor de 24 horas en circunstancias normales.

Ahorita no estoy en circunstancias normales; estoy utilizando la aplicación que guarda mis coordenadas para después poder ponerle información GPS a las fotos que estoy tomando.

Mi batería entonces dura como la mitad de lo normal; ayer pude conectarlo en el restaurante donde comimos, si no hubiera muerto. Mi teléfono tiene un modo de ahorro de energía, pero entonces detiene casi todas las aplicaciones para ahorrar energía cuando la pantalla se apaga, lo que incluye mi rastreador GPS.

Sólo hasta hoy descubrí que uno puede seleccionar aplicaciones que el modo de ahorro de energía no detendrá. Mi teléfono dice ahora que le va a durar dos días la batería; no le creo nada, pero si aguanta el día me daré por bien servido.

Los datos

Voy a estar una semana en el gabacho en total; como ya me he acostumbrado a tener datos en mi teléfono celular todo el tiempo, decidí que no quería pasar esta semana como ciego dando tumbos en la oscuridad electrónica.

Eso me parece que fue razonable de mi parte; lo que fue increíblemente estúpido, fue creer que podía confiar en Telcel para solucionar mi problema.

Me metí a la página de Telcel, y contraté 250 megabytes para usar en el gabacho. Fue relativamente sencillo, y el cobro de hecho se realizará hasta quién sabe cuándo, en mi estado de cuenta mensual. El problema fue que llegué a gabacholandia, y por supuesto no funcionó.

Les llamé para reclamarles, y después de probar varias cosas, los muy inútiles me dijeron que no podían hacer nada. Estoy usando un teléfono que no es de Telcel, porque cuando me robaron mi celular en diciembre, compré uno independientemente, justamente porque me desesperó la inutilidad de la gente de Telcel para poder sencillamente venderme un teléfono al que le pudiera poner mi chip.

Por supuesto ellos se agarraron de esto para decir que no podían hacer nada. Me dio mucho coraje, porque cuando me hartaron en diciembre con su incapacidad para simplemente venderme un teléfono, me aseguraron que no había problema conque yo comprara un teléfono por afuera y le pusiera mi chip. Y significa que lo que gasté por mis 250 megabytes básicamente se tira a la basura.

Perdí unas dos horas de mi vida ya aquí en Chicago viendo qué podía hacer al respecto, hasta que al final me resigné a haber perdido mi dinero, y salí a al menos a conocer la ciudad, aunque fuera como ciego dando tumbos en la oscuridad electrónica.

Y entonces vi un local de T Mobile.

Me metí y pregunté si podía tener 7 días de datos. “Claro”, me dijeron (sólo que en inglés). Quince minutos después, salí del local con datos en mi celular, un número básicamente ilimitado de megabytes al día (tendría que pasármela todo el tiempo viendo videos en YouTube para acabármelos), y a la mitad del precio de Telcel.

Lo que es mejor; mi teléfono (que también compré independientemente de Telcel) tiene dos ranuras para tarjetas SIM, así que puedo recibir llamadas y mandar mensajes por mi número de México, mientras recibo datos con mi número T Mobile (que sólo usaré para ello), todo al mismo tiempo.

Así que sí perdí dinero con Telcel; pero es la última vez en la vida que lo haré. Si vienen al gabacho, ni se molesten en tratar de comprar los planes de Telcel; es probable que no funcione, y es demasiado caro para una cantidad de megabytes tan pequeña. Compren una tarjeta SIM; si tienen un teléfono con dos ranuras, tienen lo mejor de los dos mundos; si tienen sólo una, usen el SIM gringo para correo, Google Maps, redes sociales, etc., y sólo pongan el SIM de Telcel cuando quieran revisar sus mensajes o correo de voz, o hacer llamadas.

Estoy considerando seriamente dejar a Telcel por otra compañía celular; se han portado de manera criminalmente incompetente las últimas veces que he tenido que lidiar con ellos.

Pero al menos tengo datos aquí, y funciona muy bien, aunque tuve que perder dinero para aprender mi lección de que no debo utilizar los servicios que provee Telcel, porque de verdad son unos inútiles.

Formas PDF

En la academia, al parecer, hay que llenar catorce millones de formas cada quince minutos, más o menos. Formas para plazas, formas para becas, formas para estímulos, formas para calificaciones, formas para viajes, formas para viáticos, formas para que nos paguen viáticos ya gastados, formas para votos, formas para pedir otras formas que entonces hay que llenar para poder hacernos acreedores a nuevas formas…

La única vez que he trabajado (fuera de la academia) en el sector público, cuando estuve en el IFE (que ya ni siquiera existe), no tuve que llenar tantas formas; pero estaba por obra determinada, así que no sé cómo sea si uno tiene un puesto “fijo”. En la industria privada jamás tuve que hacer nada por el estilo, pero tampoco creo que se pueda considerar “fijo” lo que hacía en ese entonces.

Como sea, hasta hace no mucho uno tenía que ir por sus formas a algún lado, pedirlas por favor, y llenarlas con pluma o máquina de escribir. Siempre odié eso, porque involucraba escribir a mano, lo cual nunca se me ha dado mucho (mi caligrafía de caquitas de mosca embarradas en el papel no ha tenido el éxito que yo hubiera esperado), así que fue con alegría cuando descubrí que por fin todo mundo empezó a poner formas en línea.

Al inicio muchas veces la forma era el escaneo de la forma original, si bien le iba a uno como un PDF, si no como un vil JPG. A veces daban las formas en el formato .doc de Microsoft Office, pero creo que ya casi nadie hace eso. Desde hace algunos años esto se ha ido estandarizando en utilizar PDFs editables, o formas PDF. Esto para mí es la gloria, porque entonces ya no tengo que hacer ningún truco sucio; sólo lleno la forma, salvo el documento PDF, y soy feliz.

Cuando no es editable el PDF, soy bastante menos feliz. Lo que he hecho desde hace algunos años, es recrear la forma en SVG con Inkscape, y entonces llenarla con la herramienta de texto que tiene. En general funciona bastante bien, y tengo ya una biblioteca considerablemente amplia de formitas en SVG que sólo abro para editar de vez en cuando. Dado que SVG es XML, en algunos casos simplemente pongo texto como un marcador de posición (por ejemplo, NOMBRE), y hago un script que reemplaza esa cadena por la que realmente quiera usar. Jala muy bien; el único problema es que a veces es un desmadre recrear la forma completa en SVG, pero incluso esto es evitable si la forma no es una imagen, sino que la información vectorial de la misma viene en el PDF: para esos casos sencillamente utilizo pdf2svg, o abro el PDF directamente con Inkscape.

El otro problema es cuando la forma son múltiples páginas. SVG no tiene realmente una manera de modelar documentos con múltiples páginas, entonces tengo que crear una serie de documentos (forma1.svg, forma2.svg, etc.), y hacer un Makefile o algo así para compilarlos a un único PDF. Que es la situación en la que me encuentro ahorita.

Por supuesto, clavado como soy, ya ando pensando en cómo mejorar mi flujo de trabajo. Lo obvio por supuesto sería escribir un programa que recibiera las entradas de la forma (o las leyera de una base de datos), y generara los SVG, o incluso el PDF ya directamente. Lo único que me detiene para hacer esto (además del hecho de que tengo que terminar de llenar mis estúpidas formas), es que no sé cómo obtener la dimensión que ocupa un texto determinado en SVG, y que aún teniendo esto tendría que implementar un algoritmo para partir líneas muy largas en párrafos, de preferencia tratando de que las distintas líneas todas tengan más o menos el mismo ancho. Este algoritmo ya existe, por supuesto; es el que Knuth y un estudiante suyo de doctorado diseñaron e implementaron para \LaTeX, pero tengo entendido que es suficientemente complejo como para que lo implemente yo durante un fin de semana.

Así que por mientras sigo peleándome con mis SVG separados.

La presentación

Una vez oí a alguien comparar el hacer un examen de grado de la UNAM con participar en un encuentro de lucha libre, en el sentido de que el resultado está decidido de antemano, pero eso no evita que le vayan a poner a uno una santa madriza.

Para prepararme para dicha madriza, me puse a hacer mi presentación casi desde que me dieron la fecha del examen.

Yo me considero bastante bueno dando presentaciones, y a lo largo de mi vida académica he utilizado varios programas para hacerlas (aunque obviamente jamás Power Point, dado que no uso Windows). Para la presentación de mi examen de doctorado quise hacer algo nuevo, así que primero intenté utilizar PinPoint.

El programa está simpático, pero me resultó inútil: no acepta fórmulas matemáticas, y a pesar de que tiene transiciones muy bonitas, éstas sólo funcionan entre transparencias, no objetos de las mismas. Además, no puede cargar nativamente SVGs, y como que más bien está pensado para presentaciones “corporativas” (dícese, texto con imágenes lindas de fondo). Ni siquiera encontré cómo poner dos figuras en la misma transparencia (que no involucrara combinarlas en una misma imagen de fondo).

Después intenté JessyInk (viene integrado con Inkscape). Yo soy fan rabioso de Inkscape; lo uso para todo, y aunque la mayor parte de las figuras de mi tesis son resultado de programas que yo escribí que escupían SVG, casi todas fueron retocadas con Inkscape, en muchos casos al menos para agregarle etiquetas. Sin embargo, de nuevo me decepcionó lo que hace JessyInk; las presentaciones que crea son muy apantalladoras, pero además de marear al espectador (pueden ver un ejemplo aquí), no vi nada que me ayudara a mí en lo que quería hacer: poder manipular objetos individuales dentro de cada transparencia.

Mi tesis consiste en problemas de geometría computacional que lidian con cositos dando de vueltas en el plano. Lo que yo quería era que dichos giros pudiera implementarlos dentro de la presentación misma, girando realmente los objetos en ella, en logar de girar a pie las imágenes de cada transparencia.

Un último intento fue utilizar Impress, el paquete de presentaciones de LibreOffice; pero nada más estar baboseando cinco minutos con él fueron suficientes para que me percatara de que no iba a poder usarlo para lo que quería.

Así que como durante dos horas estuve coqueteando seriamente con la idea de yo escribir un programa, donde las presentaciones fueran programas mismos que cargaran los objetos de SVG, los interpretaran como códigos de dibujo para Cairo, y yo pudiera animar cada parte de ellos de forma individual. Suena un proyecto interesante, y en Vala incluso podría salir relativamente rápido; pero ciertamente no hubiera acabado a tiempo para mi examen.

Así que me puse a girar las imágenes a pie antes de ponerlas en transparencias. Estuve a punto de usar sólo un montón de SVGs que compilara a un PDF usando un Makefile, pero al final decidí utilizar el viejo y confiable Beamer, a pesar de que no lo había usado en años.

La presentación me quedó padre, me parece; el único problema fue que la hice como si no tuviera restricciones de tiempo para exponerla, así que como dos terceras partes de la misma las di medio corriendo.

Pero eso será una historia para otro día.

La vida digital

Ayer mi mamá me pidió que la acompañara a comprar una tele en Best Buy, porque así es la vida.

De pura chiripa encontramos una tele Samsung LED FullHD de 39″ en 5,999.99 pesos, porque estaba de promoción, así que le dije que se llevara esa, porque sí estaba muy barata. Ya pagando, vi que podía pagarse a 12 meses sin intereses con tarjetas de crédito Banamex; yo acabo de sacar la mía porque llevaban meses jode y jode los de Banamex con que sacara una, y porque con ella compré mi nuevo celular, así que le dije a mi mamá que me dejara pagarla con mi tarjeta de crédito y que luego ella me pagara las mensualidades.

Por supuesto mi tarjeta no pasó, porque con ella pagué el hotel de Oaxaca durante el congreso de la semana pasada y tengo como tres pesos de crédito; pero entonces se me ocurrió una idea: saqué mi celular, abrí mi aplicación de Banamex, y pagué mi tarjeta de crédito ahí mismo en la tienda, todavía enfrente de la caja donde la acababan de rebotar. Acto seguido, compré la tele de mi mamá con mi tarjeta de crédito, que ahora sí pasó.

Es chistoso, pero fue a la vez sorprendente y natural que pudiera hacer eso. Sorprendente porque nunca lo había hecho, y si hace algunos años me hubieran dicho que podría hacer algo así lo hubiera puesto altamente en duda… comenzando por el hecho de que yo tendría una tarjeta de crédito. Natural porque, vamos, es el mundo en el que ahora vivimos.

Ahora si tan solo pudiera comprar procesadores en Amazon México…

5 veces más líneas, 400 veces más rápido

Xochitl está a veces bajo ataque. En general son ataques idiotas que tratan de entrar por SSH usando combinaciones de usuario/clave del tipo “root/root” o “user1/user1″; evidentemente eso casi nunca funciona, y además esos ataques son automáticamente detenidos después de tres intentos fallidos con denyhosts.

Esos ataques no me dan problemas; me dan problemas los ataques dirigidos específicamente contra mi blog y/o galería en línea. No porque alguna vez hayan logrado nada (los mantengo actualizados); el problema es que a veces generan una cantidad tal de solicitudes que Apache comienza a sobrecargar MySQL, la base de datos queda trabada, y Apache entonces se queda atorado sirviendo páginas. Si los atacantes solicitan muchísimas solicitudes a la vez, esto causa que MySQL quede atorado con cientos de consultas en su cola, y por lo tanto que Apache quede atorado con cientos (o miles) de páginas que quieren ser servidas.

Como Apache trata de no tirar conexiones, y cada una de ellas utiliza procesador, esto hace que el CPU de Xochitl de repente se encuentre utilizado al 117%. Aquí es donde debo mencionar que Xochitl es una pobre Pentium 4 a 2.40 Ghz; es posible (y de hecho probable) que la mayoría de los teléfonos celulares inteligentes que han salido este año sean más rápidos (y tengan más memoria) que Xochitl.

De todo lo anterior no es esta entrada.

Esta entrada es acerca de una situación que encontré mientras buscaba qué poder hacer para aliviar a la pobre de Xochitl. La más sencilla es ver qué IPs están solicitando más conexiones HTTP, y agregarlas a /etc/hosts.deny (teniendo cuidado de no negarme acceso a mí y mis máquinas, o a los robots rastreros de Google). Suele funcionar; sobre todo considerando que estos “ataques” (la verdad ya no sé si son ataques o sólo lectores ligeramente stalkeadores de mi blog/álbum) no ocurren muy seguido.

Así que hice un programita que leyera los logs (o bitácora, si quisiera usar español correcto) de acceso de Apache, sacara las IPs, y contara cuántas veces aparece cada una. Como lo primero que aparece en cada línea es la IP solicitante seguida de un espacio, con el siguiente comando obtengo todas las IPs que solicitan páginas a Apache:

cat /var/log/apache2/access_log | cut -d " " -f 1

Hasta ahí vamos bien; ahora, ¿cómo saco de ahí cuántas veces se repite una IP?, porque sabiendo eso ya puedo saber cuáles IP solicitan un número ridículo de conexiones. Siendo, como soy, programador, escribí un programita que hiciera esto por mí. Lo escribí en Python, porque lo quería rápido, y esto me salió:

#!/usr/bin/env python

import sys

if __name__ == '__main__':
    ips = {}
    for line in sys.stdin.readlines():
        line = line.strip()
        if line in ips.keys():
            ips[line] = ips[line] + 1
        else:
            ips[line] = 1
    for ip in ips.keys():
        print('%d: %s' % (ips[ip], ip))

Esas son 14 líneas de Python, incluyendo el shebang y dos líneas en blanco. El programa lee línea a línea la entrada estándar, y usa un hash table para ir contando cada aparición de una IP.

Muy contento con mi programa lo corrí… y el maldito programa corrió, y corrió, y corrió, y siguió corriendo. Al minuto lo detuve, incrédulo de que pudiera ser tan endiabladamente lento. Lo revisé, lo puse a imprimir resultados intermedios, y el resultado era el mismo: es lentísimo.

Estúpido Python.

Me subí las mangas y lo reescribí en C, usando glib porque no me iba a a poner a escribir mi propio hash table (been there, done that). Esto me salió:

#include < stdio.h >
#include < string.h >
#include < glib.h >

typedef struct _integer integer;

struct _integer {
        int n;
};

static integer*
integer_new(int n) 
{
        integer* i = g_new(integer, 1);
        i->n = n;
        return i;
}

static char*
read_line(FILE* file)
{
        char line[4096];
        int i = 0;
        line[i] = (char)0;
        int c;
        while (TRUE) {
                c = fgetc(file);
                if (c == EOF || c == NEW_LINE)
                        return strdup(line);
                line[i++] = c;
                line[i] = char(0);
        }
        return strdup(line);
}

void
print_key_value(char* key, integer* value, gpointer user_data)
{
        printf("%d: %sn", value->n, key);
}

int
main(int argc, char* argv[])
{
        GHashTable* h;
        h = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
        do {
                char* line = read_line(stdin);
                if (!strcmp(line, "")) {
                        free(line);
                        continue;
                }
                char* key;
                integer* value;
                if (g_hash_table_lookup_extended(h, line, &key, &value)) {
                        value->n++;
                } else {
                        value = integer_new(1);
                        g_hash_table_insert(h, line, value);
                }
        } while (!feof(stdin));
        g_hash_table_foreach(h, print_key_value, NULL);
        g_hash_table_destroy(h);
        return 0;
}

Esas son 65 líneas en C, incluyendo la definición medio redundante de una estructura integer porque no quise usar las macros GINT_TO_POINTER y GPOINTER_TO_INT. No es elegante.

Ya que tuve mis dos versiones, según yo, equivalentes, las corrí ambas. La salida que producen es idéntica, así que me parece que sí son equivalentes. La versión en Python tarda más o menos 1 minuto 58 segundos (más/menos dos segundos, en todas las ocasiones en que lo corrí). La versión en C tarda 0.285 segundos, consistentemente debajo de 0.290. Esto para una bitácora de 95,080 líneas, de 12 Megabytes de tamaño.

La versión en C es unas 5 veces más larga en líneas que la de Python (de hecho 4.333, pero no importa), además de que las líneas tienen más caracteres; y sin embargo tarda (en tiempo de ejecución) del orden de 400 veces menos.

Ahí está el código si alguien quiere tratar de mejorar el resultado en Python. Yo estoy sumamente decepcionado; creí que las hash tables de Python estaban decentemente optimizadas: estoy usando cadenas como llaves al fin y al cabo. Y lo peor es que la versión en C ni siquiera me tomó mucho más tiempo en escribir.

Actualización: Gracias a Omar, ya vi que estaba cometiendo un errosote al buscar la llave en la hash table de Python; no tenía porqué buscarla en .keys() cuando puedo hacerlo directamente en la tabla. Con la sugerencia de Omar, el código Python es solamente el doble de lento que el código C. Lo que de hecho tiene sentido.

Dónde estás, que no te veo

En mi departamento tengo más computadoras de las que cualquier adulto viviendo solo debería tener. Más aún si cuento cosas como mi PlayStation 3 o mi teléfono celular, o incluso mi N800, por más que no lo haya usado en los últimos dos o tres años.

Como sea, casi todas las computadoras se conectan a mi red local vía inalámbrica, y todas lo hacen a través de DHCP. Todas por lo tanto suelen tener direcciones de IP dinámicas (excepto Atom, mi servidorcito con procesador Atom; como redirijo a él las conexiones SSH de mi módem DSL, necesito que tenga una dirección estática), y después de un rato es un desmadre estar viendo cuál computadora tiene qué dirección IP.

Para solucionar esto utilizo Avahi, una implementación de zeroconf para Linux, lo que causa que cada computadora con Linux que tengo anuncie su presencia en la red local. No importa qué dirección IP tenga Centurion, mi máquina de escritorio, dentro de mi LAN puedo accederla con la dirección centurion.local (desde una máquina que use Linux y Avahi). Como hay un navegador de zeroconf para Android, incluso lo puedo usar desde mi teléfono, aunque tengo que abrir el navegador, ver qué dirección tiene centurion.local y ya usarla.

Como sea; ayer salí un rato de mi departamento y al regresar vi que se había ido la luz, ya que Atom estaba apagado. Prendí de nuevo el servidorcito y mi máquina de escritorio (la había dejado suspendida), y traté de conectarme de nuevo a atom.local. No pude, así que supuse que Atom no había iniciado correctamente. Agarré mi teclado USB y lo llevé al servidorcito, usando mi televisión de 46″ como monitor (como Atom está en el mismo mueble que la tele, lo tengo conectado a ella todo el tiempo por VGA). Cuando entré al servidorcito, vi que, aparentemente, todo estaba bien. Traté de conectarme de Atom a Centurion, y tampoco lo encontró.

El protocolo de zeroconf funciona de tal forma que, bueno, necesita cero configuración. Lo dice ahí, en su nombre. Que algo falle con Avahi es bastante raro; de hecho no ha habido nuevas versiones en años; sencillamente funciona.

Regresé a Centurion, y me puse a investigar qué podía pasar. Ambas computadoras podían accesar Internet, sólo no la una a la otra. Dado que Avahi es un protocolo distribuido (no necesita un servidor), no pensé que fuera mi módem DSL, pero ya sin muchas otras opciones me metí al mismo para ver si todo jalaba bien con él; a lo mejor algo extraño había pasado con el servidor DHCP, o quién sabe.

Y entonces en el navegador apareció la página de autenticación del módem DSL. Y resultó que no era mi módem.

Cuando volvía a prender Centurion después de que regresó la luz, en lugar de conectarse a mi módem DSL, se conectó al módem DSL de un vecino, que todavía en esta época no ha aprendido que hay que ponerle clave. Conecté Centurion a mi módem DSL, y por supuesto se pudieron encontrar él y Atom. También borré el módem DSL de mi vecino de la lista de Access Points a los cuales Centurion trata de conectarse (no tengo idea de cómo llegó ahí en primer lugar), así que espero este particular problema no vuelva a repetirse.

Photo Locator

Cuando regresé de Guadalajara de ver a mi novia, traje conmigo cerca de 200 fotografías, y a casi todas ellas les pude sincronizar la información de localización GPS que saqué usando GPS Logger; platiqué de eso hace unas semanas. Cuando mi novia vino a verme durante semana santa, su despampanante belleza causó que me distrajera lo suficiente como para olvidárseme prender GPS Logger todos los días.

A ver, no se distraigan ustedes...

A ver, no se distraigan ustedes…

Esto, aunado con el hecho de que todas mis fotos antes de mi viaje a Guadalajara no tienen etiquetas GPS, y que tomé muy pocas fotos cuando Mina vino en las vacaciones, ha resultado en que el mapa de mis fotos en mi galería en línea se vea sí:

Mapa de la galería

Mapa de la galería

Según ese mapa, he tomado más fotos en Guadalajara que en la Ciudad de México. Casi diez veces más fotos. Y cero en cualquier otro país.

No me malentiendan; no tengo nada contra Guadalajara. Está bonito por allá. Pero el hecho de que la información de geolocalización de mi galería en línea refleje (erróneamente) que he tomado más fotos allá que aquí me tiene… incómodo, por decir lo menos. Y además, no refleja nada de los fabulosos viajes que he realizado por Europa y el resto de Norteamérica.

No hay manera de que automáticamente haga que las fotos sin información GPS adquieran dicha información. Bueno; técnicamente podría hacer un script que les pusiera coordenadas aleatorias a todas mis fotos, pero eso no serviría de mucho para lo que quiero: que el mapa de allá arriba refleje mis andanzas por todo el mundo. La única forma de conseguir eso es ir foto por foto tratando de recordar dónde estaba cuando la tomé.

El problema no es acordarme; tengo una extraordinaria memoria cuando se me pega la gana, y suelo recordar con bastante precisión dónde estaba cuando tomé una foto. El problema es la interfaz para asignarles coordenadas a las fotos; el plugin de Gallery 3 que genera el mapa de allá arriba también me permite ponerle coordenadas a cada foto, pero eso es usando dos cajitas de texto.

Editor de coordenadas

Editor de coordenadas

Esto significa que me tengo que ir a Google Maps (o algún otro programa que me permita ver las coordenadas, en latitud y longitud, de un lugar), agarrar esos numeritos, y pegarlos cada uno en su lugar en las cajitas de texto. Lo intenté hacer, y como a la tercera foto ya quería aventarme a las vías del metro para terminar con mi sufrimiento. Así que hice lo que cualquier programador que se respete haría; escribí un programa para ponerles coordenadas a mis fotos viejas.

Photo Locator

Photo Locator

Realmente fue trivial; tomé la mitad del código de otro de mis programas (Quick Photo Editor), le metí un actor con un ChamplainView (de la biblioteca libchamplain)… y básicamente ya. El actor hace todo, incluyendo bajar los mapas de OpenStreetMap, y me da las coordenadas de donde yo quiera en el planeta; es de verdad una biblioteca espectacularmente fácil de usar. De hecho es más rápido para mí ponerles coordenadas a mis fotografías que escribirles el título.

El programa es para mis fotos viejas; espero seguir usando GPS Logger para las futuras (siempre y cuando la despampanante belleza de mi novia no me siga distrayendo). Pero tengo más de 9,000 fotos, así que tardaré en ponerles coordenadas a todas; más en lugares como Europa, donde tendré que meterme al Street View de Google Maps para medio ubicarme dónde estaba cuando tomé la foto. Aunque ya decidí no angustiarme tanto; si no recuerdo rápidamente el lugar exacto donde tomé una foto, me voy a conformar con atinarle a la ciudad en el peor de los casos. Y como es complicado modificar la base de datos de coordenadas con fotos que ya subí, probablemente tendré que subirlas todas de nuevo cuando acabe.

Pero al menos mi galería en línea ya no dirá en su mapa que he tomado más fotos en Guadalajara que en la Ciudad de México.

Etiquetas GPS

Para mi viaje a Guadalajara instalé GPS Logger en mi teléfono Android. Antes de tener teléfono mamón, tenía mi Nokia N800, al cual le compré un GPS bluetooth, y con el cual podía ir grabando mi posición GPS cada segundo, para después transportar esa información a mis fotos.

Nunca funcionó muy bien que digamos; para empezar, el N800 no podía estar recabando mi posición GPS todo el tiempo vía bluetooth, por la sencilla razón de que la batería se moría a las doce horas, si uno tenía suerte. Además, mi GPS bluetooth sólo era eso, GPS; no soportaba AGPS (Assisted GPS), que se ayuda de las torres de celular para aproximar la posición de uno cuando no consigue engancharse a los satélites GPS (que pasa más comúnmente de lo que uno pensaría). Pero además se volvía todo una cosa muy incómoda, cargando el N800, el GPS, la cámara, y además con la angustia de que la batería se iba a morir en cualquier momento.

La única vez que intenté usarlo así fue cuando fui a ver a mi cuate Eddie en San Francisco en el 2009, y además de que pasó todo lo que acabo de describir (se murió la batería, el GPS no se conectaba a los satélites, fue un desmadre estar cargando tantos electrónicos), cuando regresé a México y traté de sincronizar la información que el N800 había estado guardando en mis fotos, resultó que no podía porque F-Spot había cambiado las horas de las mismas. Para que esto funcione bien, el reloj del GPS y el de la cámara debe estar casi perfectamente sincronizado, obviamente.

Creo que por ahí tengo la información GPS de ese viaje a San Francisco, pero la verdad ya ni intenté ver si podía ponérsela a mis fotos. Mi N800 ahora está arrumbado por ahí, y hace años que ni siquiera lo prendo.

Con mi teléfono Android todo ya es mucho más sencillo: tiene el GPS integrado, utiliza AGPS, y además la batería es bastante mejor que el del N800, así que como dije instalé GPS Logger y estuve guardando mi ubicación todo el tiempo que tomé fotos durante mi estadía en la capital jalisciense. Hoy que ya acabé la chamba que se me había acumulado por estar tres días casi sin ni siquera consultar mi correo electrónico, investigué cómo podía sincronizar dicha información en el par de centenas de fotos que tomé durante mi viaje.

Fue bastante sencillo: hay un programita llamado GPS Correlate que lo hace automágicamente, incluso con ventanitas y así. Igual y reescribo el programa, porque la verdad sí se siente medio abandonado; pero funcionó sin ningún problema. Ya que mis fotos tenían la información de ubicación dentro de ellas, vi qué podía hacer para que mi galería en línea la utilizase. También fue sencillo; sólo instalé el módulo EXIF GPS de Gallery3, y con esto aparece un mapita de Google Maps en cada uno de mis álbumes donde haya información de localización:

Mapa de álbum

Mapa de álbum

Y por supuesto, cada foto con información de localización tiene un mapita de Google Maps individual:

Mapa de foto

Mapa de foto

Está bastante chido; vaya a ser que se me olvide dónde tomé una foto. Me da coraje no haberlo hecho antes; tengo mi teléfono mamón desde junio del 2011, y en mi megaviaje de seis meses me hubiera sido bastante útil. Pero bueno, así se aprende en esta vida. Mi Android soporta un día entero de estar usando GPS Logger, pero apenas; termina con la batería debajo del 5%. Pero ya dentro de poco me tocará renovarlo, y espero que mi próximo teléfono tenga mucho mejor batería que el de ahora (y que jale más rápido, porque ya lo siento muy lento… y que tenga un buen de memoria interna, porque la que tengo es una grosería).

Así que a partir de ahora mis fotos tendrán la información de dónde las tomé, a menos que algo le pase a mi celular.

Con la cámara de mi nueva laptop

Después de que me hicieron notar, de la manera más directa posible, que me veía horrible en la imagen que puse hace unos días de mi vieja Creative Webcam NX, decidí poner screenshot de cómo aparezco en los hangouts de Google con la cámara de mi nueva laptop:

Con la cámara de mi nueva laptop

Con la cámara de mi nueva laptop

No, la calidad tampoco es espectacular, pero hago notar que era de noche, y además es un screenshot de la página del navegador donde estaba el hangout. Ya animado se ve bastante decente.

Y ciertamente me gusta más que usar Skype. Que al parecer en menos de un mes todos los usuarios de messenger serán migrados (de forma obligatoria) a Skype; durante años utilicé el messenger, pero tiene ya meses que ni siquiera me he conectado (solía causar que trabajara aún menos al escribir la tesis). No me queda claro que lo vaya a extrañar mucho.