domingo, 26 de abril de 2015

Python y sus tipos básicos

Uno de los problemas encontrados al realizar el lado cliente de la comunicación serie es el limitado abanico de tipos de Python.
Esta tabla es muy representativa al respecto:
Type codeC TypePython TypeMinimum size in bytes
'b'signed charint1
'B'unsigned charint1
'u'Py_UNICODEUnicode character2 (see note)
'h'signed shortint2
'H'unsigned shortint2
'i'signed intint2
'I'unsigned intint2
'l'signed longint4
'L'unsigned longint4
'f'floatfloat4
'd'doublefloat8


De cara al uso de streams caben destacar dos peculiaridades:
  * No existe char, es un str (string) de longitud 1.
  * No existe byte, solo int.

Por tanto, cuando leemos de un Stream, se lee y escribe en modo str.

Esta situación nos lleva a una peculiaridad ya que para comparar bytes y visualizar las imágenes necesitamos int (==char), y por tanto un casting.
Pero si luego queremos guardar dichas imágenes es necesario volver a str (==char).

Estas conversiones de tipos se consiguen con ord() y char().

Páginas relevantes:
https://docs.python.org/3.1/library/array.html
http://www.tutorialspoint.com/python/python_variable_types.htm

De vuelta, y funcionando

Mucho ha llovido desde la última vez.
Mayoritariamente porque la actividad se ha movido a bitbucket y a google groups.

Sin embargo, merecía la pena contar las buenas noticias.
Tras bastante desarrollo, incluida la API bloqueante, la corrección de numerosos bugs y de localizar las causas por las que la cámara podía bloquearse, ya se tiene una versión plenamente funcional.
Código disponible en:
https://bitbucket.org/jjv_team/link_sprite
https://bitbucket.org/snippets/jjv_team/KrbA/lscam-triggered-photo-loop

Este es el vídeo demo:

Se han descubierto muchas limitaciones. Pero eso ya se discutirá más adelante.

miércoles, 15 de abril de 2015

Python - Serial port

Se quiere conectar el puerto serie de Arduino con algún programa externo para poder depurar y visualizar las imágenes que se están obteniendo a través de la cámara LinkSprite. Se ha decidido desarrollar unos pequeños códigos de prueba en Python con este propósito ya que se puede desarrollar rápidamente en este lenguaje y tiene la librería de OpenCV para un posterior procesado si se da el caso.

Para la conexión de Python con Arduino se ha hecho uso de la librería Pyserial que simplifica enormemente la comunicación.

- Página oficial: http://pyserial.sourceforge.net/
- Instalación para Windows: http://petrimaki.com/2013/04/28/reading-arduino-serial-ports-in-windows-7/
- Ejemplo de uso (con fallo): http://stackoverflow.com/questions/12117669/python-read-serial-output-from-arduino

Inicialización de puerto serie en Python en windows:
ser = Serial('COM6',115200,timeout=2)  # El número del COM lo miramos en Arduino->Herramientas->Puerto
Inicialización de puerto serie en Python en linux:
ser = Serial('/dev/tty.usbmodem621',115200,timeout=2)  # El número del COM lo miramos en Arduino->Herramientas->Puerto

Es importante tener en cuenta el timeout, ya que si le ponemos valor 0 lo estaremos configurando para hacer lectura no bloqueante, por lo que hay que establecer algún timeout (o dejarle con valor None para espera indefinida) para hacerlo bloqueante y ser consistente con el código de la librería LinkSpriteCam.

sábado, 11 de abril de 2015

LinkSprite - Logs de ejecución (deprecated)

La mayor parte de los logs de ejecución de estas fechas tempranas de desarrollo están contaminadas por dos problemas:
  1. Un error de programación, por el cual no se estaba reseteando a dirección de lectura (a pesar de que ya se conocía este mismo error en el código original y ya se disponía de una función que contemplaba este caso).
  2. Un problema del dispositivo, que al estar brikeado (soft-brick), toda prueba era errónea.


Se han movido dichos logs a una sección secundaria para facilitar la lectura del blog:

LinkSprite - La resurrección

Tras enviar varios comandos RESET, así como un DISABLE_POWER_SAVING, la cámara se ha puesto a funcionar.

Actualización:
Cuando ya se tenía el nivel 2 de la API, se ha probado el código actualizado a 25/03/15. El cual funciona pero requiere algunos cambios para conseguir que capture varias imágenes.
Éste también se queda bloqueado a veces dejando la cámara "inservible".  De modo que la salida solo son líneas en blanco.


Para demostrar que se leen varias imágenes, se deja un fragmento del log de depuración.
<code at 18e989f>
SendReadDataCmd... 11
SendReadDataCmd... 11
SendReadDataCmd... 11
SendReadDataCmd... 11
SendReadDataCmd... 11
SendReadDataCmd... 1end! 1
EndFlag!   readed chunck=16  size=8768
Image taken
SendTakePhotoCmd... 1
SendReadDataCmd... 1end! 1
EndFlag!   readed chunck=4  size=8
Image taken
SendTakePhotoCmd... 1
SendReadDataCmd... 1end! 1
EndFlag!   readed chunck=12  size=24
Image taken
SendTakePhotoCmd... 1
SendReadDataCmd... 11
SendReadDataCmd... 11
SendReadDataCmd... 1end! 1
EndFlag!   readed chunck=16  size=96
Image taken
SendTakePhotoCmd... 1
SendReadDataCmd... 11
SendReadDataCmd... 11
SendReadDataCmd... 11

Cada "1" significa que lo que se ha leído es exáctamente lo que se esperaba leer (es decir, los códigos de respuesta).

LinkSprite - Los esfuerzos

En este post se cuentan algunos re los esfuerzos y errores encontrados. Si bien poner todos sería extremadamente extenso, solo se contarán ciertos matices.

El código se publicará cuando esté "en limpio", es decir, listo para hacer varias pruebas sobre él. Ya que de otro modo su testeo es inmanejable.

Características destacables

Modo librería

Se han implementado las cabeceras y respuestas de la mayoría de los comandos (todos los de valor fijo), así como funciones de atajo.
De este modo, se puede usar la librería a 3 niveles:
   1. A nivel de byte, usando los mensajes definidos.
   2. A nivel de mensaje, usando la función sendCommand.
   3. A nivel de función, usando funciones específicas.

Multisoporte

El código descansa sobre Stream. Luego cualquier implementación de puerto serie que herede de ella se puede utilizar de forma nativa.
Además, para exponer dicha implementación, se expone la variable serialPtr, que gracias al uso de templates no requiere casting para utilizar cualquier extra de dicha implementación.

Ejemplo:


Facilidades

Muchas veces, especialmente al inicio, no interesa conocer qué devuelve el comando. Basta con enviarlo y esperar que funcione. Aunque el uso correcto es comprobarlo.
Por ello se implementan dos utilidades
    consumeData() - que "limpia" el buffer de lectura
    checkResponse() - que verifica si la respuesta esperada y recibida son iguales


Errores encontrados

Envío incorrecto de los mensajes

Tras leer la documentación de Serial, especialmente los comandos write(), print(), y su clase básica Stream, he descubierto que el parche propuesto por la profesora es un error grave cuyas consecuencias esperemos que no sean catastróficas (cámara rota). Sin embargo, el código de la documentación oficial tiene el mismo error de criterio, luego algún fallo de concepto se ha perdido por el camino (quien haya actualizado/implementado el código de ejemplo, o quien haya decidido cuál es el comportamiento de print (core <1.0 --> core >1.0)).

Actualización
El código referido es para arduino < 100. El uso de print a partir de entonces (#include <Arduino.h>) debe ser exclusivamente para comunicación en modo ASCII.
Por tanto, cualquier Serial.print anterior a 100 debería ser sustituido por write.


Según la documentación, print convierte cualquier argumento a string. Luego en vez de enviar bytes se están enviando chars. Pero además con el problema adicional de que son "iguales" a la vista, luego su valor a nivel de bit es diferente.
Prints data to the serial port as human-readable ASCII text. This command can take many forms. Numbers are printed using an ASCII character for each digit. Floats are similarly printed as ASCII digits, defaulting to two decimal places. Bytes are sent as a single character.
For example:
Serial.print(78, BIN) gives "1001110"
Serial.print(78, DEC) gives "78"
Serial.print(78, HEX) gives "4E"
Serial.println(1.23456, 0) gives "1"
Serial.println(1.23456, 2) gives "1.23"




Veamos la situación:
El código
[...]

El cambio
    ss->write(MH);
    ss->write(ML);
    ss->write((byte)0x00);
    ss->write((byte)0x00);
    ss->write(KH);

    ss->write(KL);

Tras este cambio, el código se parece a [LinkSptite Forum Thread].


No válido para múltiples capturas

El código base está entrelazado y con números mágicos.
Por ejemplo el uso del tamaño del buffer, no solo como número mágico, sino que además en diferente escritura (decimal y hexadecimal).

Y lo más importante, falta parte del flujo de uso.
Tras tomar la foto es necesario "cerrar" la petición, cosa que no se realizaba. [actualización: stop_take_picture solo se emplea para hacer un halt, luego si la imagen se ha leído completamente dicho comando devolverá "error" (acabado en 0x01) ].
Así como reestablecer el "puntero remoto", que marca el desplazamiento del pixel a recuperar.


Uso incorrecto de una API de entrada salida no bloqueante

Serial está definido como un I/O no bloqueante. Por tanto, cuando no hay datos que leer devuelve -1.
Cabe destacar, que muchas implementaciones no bloqueantes no definen el comportamiendo de available(), por ello siempre devolverá 0.

Por tanto, en lugar de ver si hay datos disponibles, se lee directamente el puerto. Y en caso de ser -1 se cancela el resto de la ejecución (continue).

Destacar de no ser por available, este valor se estaba convirtiendo sin comprobar a byte, dando lugar a 0xFF. Situación que se ha visto a lo largo del desarrollo.
[actualización: el problema de available()==0 era que la cámara estaba bloqueada (soft-brick)]

Otros problemas menores

Valores hexadecimanes clavados a pelo, cuando deberían ser variables. El ejemplo más crítico: el tamaño de la imagen, todavía mantenido en sendReadDataCmd. (refiérase a KM KL).

LinkSprite - La conclusión: No funciona

http://www.linksprite.com/upload/file/1291522825.pdf

Tras reimplementar toda la comunicación, el problema básico es que la cámara no envía nada: -1
Este comportamiento se ve como anómalo ya que nada más encenderse debería enviar "información basura" así como una cadena de finalización (que indica que la cámara está lista).

La prueba más básica a realizar consiste en:

void setup()
{ 
    Serial.begin(19200);
    Serial3.begin(38400);
    
    Serial.println("Init done.");
    delay(1000);
    
    for (int i=0; i<4; i++){
        int read = Serial3.read();
        if (read<0) Serial.println(read);
        else Serial.println(read, HEX);
    }
    camera.sendCommand(LS_Y201_Infrared::RESET_COMMAND);
    for (int i=0; i<4; i++){
        int read = Serial3.read();
        if (read<0) Serial.println(read);
        else Serial.println(read, HEX);
    }

}


Lamentablemente, la respuesta a ambos es -1.

Este resultado ocurre tanto para SoftwareSerial, como para HardwareSerial. Luego el problema es más profundo de lo que se temía al principio.

viernes, 10 de abril de 2015

SoftwareSerial y HardwareSerial

Al realizar la refactorización del código, y además pretender que sea válido para ambos tipos de comunicaciones, se ha podido ver la forma en que estas librerías están definidas.

Referencias:
   Serial
   SoftwareSerial
Veamos un fragmento de código:

HardwareSerial.h
    #if defined(UBRRH) || defined(UBRR0H)
      extern HardwareSerial Serial;
      #define HAVE_HWSERIAL0
    #endif
    #if defined(UBRR1H)
      extern HardwareSerial Serial1;
      #define HAVE_HWSERIAL1
    #endif
    #if defined(UBRR2H)
      extern HardwareSerial Serial2;
      #define HAVE_HWSERIAL2
    #endif
    #if defined(UBRR3H)
      extern HardwareSerial Serial3;
      #define HAVE_HWSERIAL3

    #endif


En él podemos ver que Serial, Serial1, etc son variables globales que:
  1. se definen en la cabecera para divulgar su existencia
  2. se instancian en el cpp (porque es la ley)


De este modo, solo tenemos un .h (HardwareSerial.h), un cpp con la lógica (HardwareSerial.cpp) y además, un cpp por cada instancia (¿por qué? está explicado en el fichero, ¡léelo!)


Uso práctico

Tras conocer la arquitectura, y sobre todo, la jerarquía de herencia:
Stream
   SoftwareSerial
   HardwareSerial
      Serial
      Serial1
      Serial2
      Serial3
Donde Stream define las funciones usadas por el código a refactorizar, el diseño correcto consiste en: (a) Una clase que incorpore la lógica, (b) un puntero a Stream que sirva para pasar la referencia al puerto serie.
<code at >

Recurdar que Serial1 ya son variables, y que no se deberían duplicar. Por ello en caso de usar SoftwareSerial habrá que crear una variable global.

Una mejora es utilizar plantillas [1]. Aunque a veces son puñeteras, especialmente porque no se pueden definir en el cpp (ver [“Undefined reference to” template class constructor]), la mejora es notable.
<code at >




PD: recordatorio
What is the difference between const int*, const int * const, and int const *?


Arduino y múltiples ficheros

Aunque empezar esta andanza sin saber si con SoftwareSerial o HardwareSerial se podrá establecer comunicación con la cámara es una "pérdida de tiempo". Creo que ubicar el código de forma más genérica y elegante permitirá saber más acerca de las tripas de Arduino, además de permitir un intercambio rápido entre estos dos tipos de puertos.


En realidad, la inclusión de múltiples ficheros en el proyecto es "fácil". Sin embargo ha sido complicado debido a un problema con la interfaz gráfica.

El procedimiento que ha funcionado es:

  • Sacar el código a un .h y .cpp
  • Guardarlo en un directorio diferente al proyecto
  • Importarlo desde el IDE:
    • Programa > Añadir fichero...

Para divertimento del lector, y que se pregunte ¿por qué?, el procedimiento que falla es:
  • Crear/tener/copiar los .h y .cpp en el directorio del proyecto
  • Importarlo desde el IDE
Sin embargo, no solo no se importa (no aparecen en las pestañas), sino que además borra el fichero.

Por tanto, el problema de trabajar con .h locales es que se ha realizado la secuencia de pasos que tiene un bug.

La solución a este comportamiento es muy sencilla: cerrar el IDE y volverlo abrir. Refrescará el proyecto e incluirá cualquier fichero que entienda (.ino, .pde .h .c .cpp)

Información general

Se ha de saber que arduino IDE trabaja con ficheros ino. Este tipo de fichero es un fragmento que se incrusta en un cpp prefabricado, con la mayoría de includes y otras tareas de arranque hechas.
El .ino que coincide con el nombre del proyecto (nombre directorio == nombre fichero) es el main (el que se incrustará en el main.cpp).

Sin embargo también se pueden emplear .h, .c y .cpp

Para mucha más información al respecto, los recursos útiles empleados han sido:

Setup Camara (LinkSprite)

LinkSprite JPEG Color Camera TTL Interface - Infrared

Cableado

[imagen (jose)]

rojo: RX (digital)
marron: TX (digital)
morado: tierra (GND)
gris: alimentacion (5V)


Opciones:
  1. Usar puertos digitales y SoftwareSerial (código proporcionado que no funciona)
  2. Usar los tx y rx adicionales de la placa arduino mega
  3. No usar arduino


Código

LinkSprite Wiki/JPEG 2M Pixel Color Camera Serial Interface(TTL level)
LinkSptite Forum Thread
http://robotic-controls.com/learn/arduino/linksprite-jpeg-camera


Arduino Setup

Linux

Entorno de prueba:
* Ubuntu 14.04 x64 / arduino 1.6.3 x64
* Ubuntu 12.04 x32 / arduino 1.6.3 x32


Mi Instalación

(sin apt-get)
1. Arduino 1.6.3 linux x64 (bajar de http://arduino.cc/)
2. /develop/arduino_environment/IDE/arduino-1.6.3

cat /develop/arduino_environment/IDE/Arduino 1.6.3.desktop
copiar a /usr/local/share/applications
[Desktop Entry]
Version=1.0
Type=Application
Name=Android Studio
Exec="/develop/arduino_environment/IDE/arduino-1.6.3/arduino" %f
Icon=/develop/arduino_environment/IDE/arduino.png
Categories=Development;IDE;
Terminal=false
StartupNotify=true
StartupWMClass=jetbrains-android-studio
Name[es_ES]=Arduino 1.6.3

UDEV rules

Por defecto arduino se reconoce. En el caso de Arduino Mega aparece como /tty/ACM0.
Sin embargo, los permisos por defecto son:
  $ ls -l /dev/ttyACM0
  crw-rw----  1 root dialout   166,   0 abr 10 12:43 ttyACM0

Para solventarlo, hay que crear una entrada de udev:
  $ cat /etc/udev/rules.d/99-arduino.rules
  #Minimal
  SUBSYSTEM=="tty", ATTRS{manufacturer}=="Arduino*", OWNER:="root", GROUP:="dialout", MODE="0666"
  SUBSYSTEM=="tty", ATTRS{manufacturer}=="Arduino*", OWNER:="root", GROUP:="dialout", MODE="0666", SYMLINK+="arduino%n"

Estas reglas son propias, luego puede que haya errores. A priori debería cubrir todos los casos (ser más genéricas).
La información requierida al respecto se ha encontrado aquí [1],[2],[3]



Finalmente, para que estos cambios tengan efecto, hay que recargar las reglas. Se puede reiniciar, o realizar la solución eficiente:
  $ sudo udevadm control --reload

Con esto ya tenemos los permisos correctos (nobody):
  crw-rw-rw- 1 root dialout 166, 0 abr 10 12:45 /dev/ttyACM0


Para obtener más información del dispositivo, y por ejemplo, crear reglas ad-hoc, viene estupendamente bien este comando:
  $ udevadm info -a -n /dev/ttyACM0