Erwin.Ried.cl / Desarrollos / Windows /
El problema de la lentitud en la presencia de Bluetooth

Existe una librería utilizada por muchos proyectos llamada RXTX JAVA Communication API, la cual es una alternativa al vetusto Javacomm. Utilizando uno de esos proyectos noté que muchas operaciones se ralentizaban cuando tenía conectado mi adaptador Bluetooth o tenía activo el integrado del equipo. ¿cuál será el misterio? (10/08/2009 07:34 PM)


A veces, cuando se presenta un problema, la solución parte por descubrir las condiciones que gatillan el mismo. Muchas veces debemos conformarnos en convivir con estas pequeñas molestias sólo por el hecho de no poder describir cuándo y por qué pasan.

Nota
El programa en el que basaré este documento es el editor de lenguaje wiring para las placas programables arduino. El mismo está disponible, su código fuente y binarios para Mac, Windows y Linux en http://www.arduino.cc.


1. Introducción

La identificación del problema se basó en dos condiciones base, replicables del problema. Misteriosamente había ocasiones en que el entorno de desarrollo afectado se demoraba más de diez segundos en aparecer, algo inusual incluso considerando que se trata de una aplicación desarrollada en Java.

Como suelo programar en sólo una placa de desarrollo a la vez (desde ahora Arduino), mientras se realizan cambios y "uploads" de código al Arduino mismo aparentemente todo funciona bien. Sin embargo una de las condiciones cuando se cambia a otro Arduino es modificar el puerto de conexión el cual está en la opción "Tools" del entorno mismo:



Esta operación era lentísima, casi al nivel de iniciar el entorno nuevamente. El problema era en sí gatillado por el acceso al menú y no la selección del puerto por lo que rápidamente asocié el problema al código de despliegue de ese menú.


2. Inspección

Ya con suficientes pistas para investigar, primero indagué dentro del código del entorno. Generalmente prefiero revisar los archivos binarios que presentan el problema, aunque no sea la forma más directa.

Partimos entonces inspeccionando el programa de Arduino, aunque ya sabemos que está escrito en Java para en cierta parte permitir un funcionamiento en múltiples plataformas, no hay nada malo en comprobarlo rápidamente con el Administrador de tareas:




3. Disección de los archivos JAR

Como ya identificamos la relación del entorno arduino con java, naturalmente debemos encontrar el archivo JAR que contenga la posible falla. Buscando en las relaciones directo desde Process Monitor tenemos:



Ahora con DJ Java Decompiler buscamos el archivo principal del IDE, y aunque no lo crean se me ocurre acertadamente que es uno de los que pese más dentro de los usados por la aplicación:



Descompilamos y buscamos la sección referente al menú que se paraliza. En efecto les daré una versión resumida del cuento, primero el inicio del menú "Tools":

Atención
La sintaxis entregada por el descompilador puede ser extraña, incorrecta e ilegible. Generalmente es útil sólo para procesos de investigación o por ejemplo para tener un mejor referente cuando la edición debe ser realizada directamente sobre el bytecode.


Código:
protected JMenu buildToolsMenu()
{
  serialMenuListener = new SerialMenuListener();
  JMenu jmenu = new JMenu("Tools");
  JMenuItem jmenuitem = newJMenuItem("Auto Format", 84, false);
  jmenuitem.addActionListener(new  Object()     /* anonymous class not found */
    class _anm15 {
  }

  );
  jmenu.add(jmenuitem);
  jmenuitem = new JMenuItem("Copy for Forum");
  jmenuitem.addActionListener(new  Object()     /* anonymous class not found */
    class _anm16 {
  }
);


Más abajo siguen muchas otras opciones del menú, ninguna de ellas representa riesgo como para paralizar el programa. Sin embargo en la lista de puertos "com" hay algo sospechoso:

Código:
serialMenu = new JMenu("Serial Port");
  populateSerialMenu();
  jmenu.add(serialMenu);


Y más abajo, el único procedimiento sospechoso:

Código:
protected void populateSerialMenu()
{
  serialMenu.removeAll();
  boolean flag = true;
  try
  {
    for(Enumeration enumeration = CommPortIdentifier.getPortIdentifiers();
enumeration.hasMoreElements();)
    {
      CommPortIdentifier commportidentifier = (CommPortIdentifier)enumeration.nextElement();
      if(commportidentifier.getPortType() == 1)
      {
        String s = commportidentifier.getName();
        JCheckBoxMenuItem jcheckboxmenuitem = new JCheckBoxMenuItem(s,
s.equals(Preferences.get("serial.port")));
        jcheckboxmenuitem.addActionListener(serialMenuListener);
        serialMenu.add(jcheckboxmenuitem);
        flag = false;
      }
    }

    if(!flag)
      serialMenu.setEnabled(true);
  }
  catch(Exception exception)-
  {
    System.out.println("error retrieving port list");
    exception.printStackTrace();
  }
  if(serialMenu.getItemCount() == 0)
    serialMenu.setEnabled(false);
}



4. El origen del problema

La primera duda aparece ahora sobre el código Java. En una instancia anterior me encontré con que un error del entorno (en particular al hacer uso de la opción "Copy to forum") era un simple error de una comprobación:

Código:
char c = segmentArray[segmentOffset + offset];
if (segmentOffset + offset < limit) { ... 


En donde se puede ver que la asignación debe hacerse luego de la comprobación para no producir los clásicos errores del tipo ArrayIndexOutOfBoundsException:

Código:
if (segmentOffset + offset < limit) {
char c = segmentArray[segmentOffset + offset];


(Más información sobre este bug ya corregido en el foro de la comunidad Arduino)

Esta duda me hace revisar detenidamente el código del procedimiento que obtiene la lista de puertos "com" disponibles.


5. El primer intento de solución

Como en la obtención de puertos yace el problema de lentitud, y sin siquiera tener ninguna placa arduino conectada al equipo, los únicos posibles culpables son los puertos virtuales añadidos por mi adaptador bluetooth y los físicos del equipo. Se pueden imaginar qué pasaría si deshabilito el adaptador bluetooth gracias al siguiente video:



Bien, ya tenemos un primer workaround. Es rústico, pero deshabilitar programáticamente el adaptador bluetooth nos permitiría tener un entorno ágil. En el video utilizo devcon, pero en un principio se conseguiría el mismo efecto si utilizáramos el administrador de dispositivos del sistema (devmgmt.msc).

Acerca de devcon
Esta utilidad permite administrar remota y localmente los dispositivos de hardware de un equipo. Es de Microsoft y está disponible como complemento aquí.


Todo esto del primer intento de solución nos lleva a revisar nuevamente el código Java...

¿De dónde viene CommPortIdentifier? ¿Cómo obtiene esos identificadores? ¿Por qué el sencillo hecho de obtener el nombre de los puertos virtuales del bluetooth demora tanto tiempo?


6. La verdadera solución

CommPortIdentifier viene directo desde el comienzo del archivo fuente del editor:

Código:
import gnu.io.CommPortIdentifier;


¿Y de donde viene el espacio de nombres "gnu"?, pues claro, de una librería. ¿Apostarían cual?



Ahora, adivinan, seguimos escarbando.

No entiendo el porqué, pero siempre me da un gusto particular investigar estas aplicaciones por vías alternativas. Lo curioso es que en este caso tenemos todo el código fuente disponible, pero montar el escenario de desarrollo para depurar ese código es sin duda mucho más esfuerzo que indagar rápidamente gracias a estas hermosas herramientas que siempre están en mi "maletín" de soluciones.

Dentro de CommPortIdentifier.class finalmente empezamos a llegar a una vía de escape de Java. La misma comienza precisamente aquí:

Código:
public static Enumeration getPortIdentifiers()
{
    CommPortIndex = null;
    try
    {
        CommDriver commdriver =
(CommDriver)Class.forName("gnu.io.RXTXCommDriver").newInstance();
        commdriver.initialize();
    }
    catch(Throwable throwable)
    {
        System.err.println(throwable + " thrown while loading " +
"gnu.io.RXTXCommDriver");
    }
    return new CommPortEnumerator();
}


RXTXCommDriver inicia nuestra nueva aventura. Una aventura llena de nuevos misterios, alejados de la protectora máquina virtual de Java, libres, llenos de posibilidades de generar excepciones no manejadas, bienvenidos al universo de C.

La librería que comienza su nombre por RXTX implementa una interfaz común de comunicación por puerto serie para diversas plataformas, Linux, Windows, Mac e incluso algunas más minoritarias como FreeBSD. Cada implementación de las funciones tiene su código para cada plataforma y esto hace ver la necesidad de romper la universalidad de un código intermedio, en cuanto nos vamos acercando al mundo físico.

Anteriormente rechacé la existencia del código de fuente del programa en Java. En este caso, los fuentes de C de la librería (http://users.frii.com/jarvi/rxtx/) de comunicación serial valen oro. El problema ahora sigue en el archivo SerialImp.c:

Código:
 #ifndef WIN32
  pid = getpid();
#else
  char full_windows_name[80];
#endif /* WIN32 */

  ENTER( "RXTXPort:testRead" );
#ifdef TRENT_IS_HERE_DEBUGGING_ENUMERATION
  /* vmware lies about which ports are there causing irq conflicts */
  /* this is for testing only */
  if( !strcmp( name, "COM1" ) || !strcmp( name, "COM2") )
  {
    printf("%s is good\n",name);
    sprintf( message, "testRead: %s is good!\n", name );
    report( message );
    (*env)->ReleaseStringUTFChars( env, tty_name, name );
    return( JNI_TRUE );
  }
  (*env)->ReleaseStringUTFChars( env, tty_name, name );
  return( JNI_FALSE );
#endif /* TRENT_IS_HERE_DEBUGGING_ENUMERATION */
#ifdef WIN32
  strcpy( full_windows_name, DEVICEDIR );
  strcat( full_windows_name, name );
  ret = serial_test((char *) full_windows_name );

  ret = serial_test((char *) name );
  (*env)->ReleaseStringUTFChars( env, tty_name, name );
  return(ret);
#endif /* WIN32 *


En el fragmento de código dedicado a la plataforma Windows podemos apreciar que cíclicamente llamamos a "ret = serial_test((char *) full_windows_name );" y "ret = serial_test((char *) name );" con el fin de comprobar si cada puerto puede ser abierto. En Windows, aunque no lo parezca, cada puerto y dispositivo es también un archivo, y aunque la interfaz de usuario lo mantiene como un completo secreto aplicando capas sobre esta abstracción, generalmente a bajo nivel seguimos utilizando los recursos como ficheros sin el alcance de "dispositivo de una clase en particular" del que estamos acostumbrados.

Esto significa que dentro de la ruta //./ estamos haciendo referencia al ordenador local, sobre un recurso en particular (todo esto relacionado a UNC, Universal Name Convention, utilizado como notación de rutas en Windows). Así //./COM5 será, dentro de nuestro equipo, el archivo COM5, es decir la representación del puerto de comunicaciones cinco. Toda esta breve reseña era para explicarles un poco los detalles de la lógica del funcionamiento de "serial_test":

Código:
unsigned long *hcomm;
hcomm = CreateFile( filename, GENERIC_READ |GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0 );
if ( hcomm == INVALID_HANDLE_VALUE )
{
  if (GetLastError() == ERROR_ACCESS_DENIED)
  {
    ret = 1;
  }
  else
  {
    ret = 0;
  }
}
else
{
  ret = 1;
}
CloseHandle( hcomm );


¿Sencilla o no?, si logro abrir filename, entonces devuelvo un uno, si tengo un error devuelvo un cero.

Ahora... ¿Cuál es el problema en esta lógica?, aparentemente no existe problema pues si un puerto es un archivo, si logro abrirlo entonces ese puerto existe y como desconozco que puertos existen debo recorrerlos encuestando puerto por puerto.

El problema primero radica en términos de comportamientos sincrónicos y asincrónicos (http://msdn.microsoft.com/en-us/library/aa365683(VS.85).aspx), necesito lograr que abrir el puerto no bloqueé las siguientes operaciones. Sin embargo esto no es tan sencillo, el sólo hecho de manejar esas respuestas (por ejemplo los errores) asincrónicamente cambia nuestro esquema.

¿Qué pasa entonces con los puertos virtuales?

Windows intenta abrirlos y como son puertos virtuales, entonces el controlador del adaptador bluetooth intenta comunicarse con el dispositivo físico al que se asignó el puerto, por ejemplo un teléfono celular. Como demoran un tiempo en responder, y si demoran mucho se considera que no responden, el sólo efecto de abrir estos puertos virtuales supone una demora.


7. La solución real está casi materializada

Mi primera prueba entonces es comentar la línea de lectura en "serial_test". El resultado es que fácilmente puedo lograr que todos los puertos posibles e incluso los inexistentes aparezcan o simplemente que ninguno aparezca como parte de las opciones en el entorno de Arduino. Esto ya es un buen comienzo.

Gracias a un código de ejemplo (http://www.codeproject.com/KB/system/enumports.aspx) descubrí que existía otra forma de comprobar los puertos. Esto ya es una mejor continuación.

Windows NT permitía ya desde tiempos memorables obtener todos los puertos disponibles sin tener que intentar abrir ninguno en particular. El truco está en la utilización de la función QueryDosDevice.

Así nuestra remozada función queda:

Código:
/*----------------------------------------------------------
serial_test

   accept: filename to test
   perform:
   return:      1 on success 0 on failure
   exceptions:
   win32api:    CreateFile CloseHandle
   comments:    if the file opens it should be ok.
----------------------------------------------------------*/
int serial_test( char * filename )
{
    int ret = 0;
    
    // Getting the Windows Version
    OSVERSIONINFO osvi;
    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    BOOL bGetVer = GetVersionEx(&osvi);
    
    // Using the QueryDosDevice API (on NT)
    if (bGetVer && (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT))
    {
        // This solution is based on http://www.codeproject.com/KB/system/enumports.aspx
        TCHAR szDevices[65535];
        DWORD dwChars = QueryDosDevice(NULL, szDevices, 65535);
        
        if (dwChars)
        {
          int i=0;
              
          for (;;)
          {
            //Get the current device name
            char* pszCurrentDevice = &szDevices[i];

            if (strlen(pszCurrentDevice) > 3 && strcmp(pszCurrentDevice,filename)==0)
            {
                ret = 1;
                break;
            }
            
            // Go to next NULL character
            while(szDevices[i] != '\0')
              i++;
            
            // Bump pointer to the next string
            i++;
            
            // The list is double-NULL terminated, so if the character is
            // now NULL, we're at the end
            if (szDevices[i] == '\0')
              break;
          }
        }
    }
    else
    {
        // Buggy way to test if we can open the comport (on Win9x)
      unsigned long *hcomm;
      hcomm = CreateFile( filename, GENERIC_READ |GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0 );
      if ( hcomm == INVALID_HANDLE_VALUE )
      {
        if (GetLastError() == ERROR_ACCESS_DENIED)
        {
          ret = 1;
        }
        else
        {
          ret = 0;
        }
      }
      else
      {
        ret = 1;
      }
      CloseHandle( hcomm );
    }
  return(ret);
}


Es curioso que aunque esta solución funciona a la perfección, por motivos de compatibilidad ve disminuída su posible velocidad máxima.

Esto es, si dan un vistazo al código anterior, apreciable con claridad en la sentencia en donde se utiliza QueryDosDevice. La función serial_test pregunta una vez para cada puerto, sin embargo QueryDosDevice tiene la lista de todos los puertos disponibles al instante, sin la necesidad de recorrer puerto por puerto.

Aunque por el momento dejo abierto el desafío para algún ávido lector, pues para mí, pasar de unos notorios treinta segundos de espera por operación a unos inmensurables microsegundos ya es suficiente.


8. Descarga

La librería resultante sirve para solucionar este problema en cualquier aplicación que use de RXTX.

En nuestro caso actual, solo basta con reemplazar el archivo rxtxserial.dll del directorio de Arduino para obtener la mejora:

files/articles/arduino_bluetooth_001/rxtxSerial-2.2_fixed_2009-08-04.rar


9. Conclusiones

A veces, la equivalencia entre poder modificar y reparar algo versus la existencia de código fuente del mismo no es tan crucial como pueda imaginarse. La astucia y lógica son grandes armas que nos permiten superar muchas barreras.

En el caso del documento actual, me sentí abrumado por instantes al intentar entender el trabajo ajeno, muchos estilos diferentes de programación ponen sabor adicional al trabajo de intentar reparar detalles tan ínfimos como el actual.

Haga clic sobre una de las estrellas para calificar este artículo.

Opiniones y comentarios (Escribir un nuevo comentario)
Perdona me confundí con esta página&#8594; http://www.danielclemente.com/amarok/
Escrito por Jakeukalane (Alberto (10/10/2011 03:12 PM)
Interesante la página que me mandas, pero de confundirlas... está bien que ambas sean feas :) pero son distintas
Escrito por Erwin Ried (10/10/2011 04:39 PM)
Muy bueno tu artículo. Aprendí mucho de su análisis de la biblioteca rxtx. Gracias.
Escrito por Eduardo (16/09/2011 07:30 PM)
por cierto he estado buscando el super artículo del servidor y no lo he encontrado que tristeza porqué tenía ganas de leerlo ya sabiendo algo más del tema.... :-(
Escrito por Jakeukalane (Alberto (27/02/2010 02:15 AM)
No entiendo a cual te refieres
Escrito por Erwin Ried (03/03/2010 02:00 AM)
estoy empezando a seguir tu blog. me leí hace algún tiempo lo de los captchas y lo del servidor..! le echaré un ojo al código php de la página. ah y voté sin querer en la encuesta de win7 vs winxp... yo ya no uso windows y utilicé winvista poco más de tres días... pues eso que en la encuesta marqué mejor que xp, cuando quería poner igual que siempre.... fallo del touchpad, lo tengo que mirar se clica solo muchas veces...fallo de hardware o demasiado compiz XD Bueno, aprovecho este comentario para felicitarte por tan genial blog, que he encontrado buscando temas tan diferentes como servidores o como detalles de sistema operativo. Gracias y saludos!!!
Escrito por Jakeukalane (Alberto (27/02/2010 12:54 AM)
ERWIN! estas 2do mejor del mundo en el juego music challenge de facebook!! jajaja
Escrito por Diego (30/09/2009 07:18 PM)
Lo que interesante que el se evalúe la plataforma en tiempo de ejecución, más cómodo así. Minoritarias != populares, por otro lado, en ese nivel de abtracción *BSD no es menos popular que OSX, porque OSX es un BSD (está basado en). http://en.wikipedia.org/wiki/Darwin_(operating_system)
Escrito por MAbeeTT (09/08/2009 09:02 PM)
Estimado, como siempre otro excelente articulo. Bueno te leo vez que publicas algo. Ví por otro sitio (el Francotirador) que estas usando Windows 7 que versión estas ocupando? Piensas poner algun Review de este sistema que ya se viene? Saludos PD: La verdad me inquieta saber si ya tienes el RTM legal en exclusiva jajaja
Escrito por Patricio (06/08/2009 03:34 PM)
Pensaba escribir algo, pero como no tiene errores... (jejeje)
Escrito por Erwin Ried (15/08/2009 09:25 PM)

Copyright © 2013 por Erwin Ried.