viernes, 15 de julio de 2016

Conectar Raspberry Pi Zero con Raspbian en modo Host OTG desde Linux Mint

Después de experimentar un rato con el Raspberry Pi Zero, logré encontrar la manera de configurarlo de manera consistente para poder conectarlo con un cable normal micro USB al computador, de modo que el otro lo vea como una conexión de red a través del mismo USB.

La misión: energía y datos con un solo cable USB.

Así se puede conectar en forma remota al Raspi Zero (ejemplo con SSH) y controlarlo desde el notebook por ejemplo, sin tener que usar un host usb externo, teclado, mouse y pantalla HDMI. El truco es habilitar el modo OTG (modo host en que el computador percibe al RPi como un periférico), que esta puerta USB la detecte como un dispositivo de red, que obtenga dirección IP válida para conectarse, e incluso que se pueda compartir el acceso a internet al Rpi. ¿Poca cosa, jaja?
;-)
RPi Zero conectado a notebook por USB (alimentación y datos) y con acceso a internet.

Algo de historia y contexto

   Aunque se estaba experimentando desde que se puso a la venta el Zero en noviembre de 2015, el soporte OTG para Raspbian estaba algo complejo al principio de habilitar, pues requería usar un kernel especial o compilarlo uno mismo, levantar unos módulos del kernel en forma manual y configurar y configurar... como se puede ver en este blog.
   Sin embargo, todo el avance de estos pioneros permitió habilitar los drivers e incluso configurarlos apropiadamente usando overlays, de modo que luego de integrarlo en la entrega del 10/05/2016 del sistema operativo Raspbian, es muy fácil utilizarlo... aunque bastante difícil de encontrarlo explicado de una manera fácil, y menos en español.
   Me basé harto en el post Raspberry Pi Zero – Programming over USB! (Part 2) de Andrew Mulholland (¡admirable!), pero los comentarios de otros usuarios aportaron también mucho a resolver problemas que explico más abajo. En ese post base las instrucciones son mas sencillas, pero con la experiencia adquirida podemos configurar cosas en ambos extremos para que el Zero consiga IP automáticamente, que el Linux Mint lo reconozca siempre bajo la misma interfaz de red y que por lo tanto siempre le comparta internet al Zero.

Breve explicación técnica

   El Raspberry Pi Zero y el Compute Module son diferentes al resto de los Rpi, en el sentido de exponer una sola puerta USB desde la CPU. Los modelos con más de una puerta USB como los B, B+, 2 y 3 lo que hacen es usar un chip (LAN9512 o LAN9514) que provee una puerta LAN y varias  puertas USB vía un hub USB, compartiendo todos el mismo ancho de banda. La única puerta USB directa desde la CPU se configura por defecto en modo "periférico" ("peripheral" en la documentación del overlay para dwc2) para que un dispositivo USB al conectarse al Rpi sea tratado de ese modo. Como esa conexión intermedia entre el SOC y hub USb no es accesible, tampoco se puede utilizar para habilitar el modo "anfitrión" ("host" según la misma documentación) o "otg" que abarca ambas opciones.
  Sin embargo, el Rpi Zero sí tiene expuesto este puerto directo, por lo que podemos habilitar el modo mixto OTG, cuyo nombre indica que se selecciona el modo de operación sobre la marcha, habilitando que se comporte como un puerto normal si enchufamos un pendrive, teclado o mouse, en que el RPi manda, o como un dispositivo periférico de un computador, en que éste manda.

Paso a paso

   Se requiere tener la tarjeta SD ya cargada con Raspbian (mínimo con la versión Jessie 2016-05-10), lo que se puede encontrar en otras guías en internet (ejemplo "instalar raspbian en sd").
Con eso listo, utilizando el mismo Zero o desde el computador corriendo Linux Mint (en mi caso aún con la versión 17.3 Cinnamon) podemos cambiar unas opciones para que reinicie con ese soporte habilitado.

Alternativa sin usar el Raspberry Zero:

   Lo anterior supone que se tiene iniciar el Zero al menos una primera vez para preparar todo, necesitando conectar teclado, monitor, red y pantalla. Sin embargo se tiene la opción de reutilizar otro Raspberry Pi que ya esté conectado para hacer los cambios de los pasos 1 al 4, aunque sea de otro modelo, pues con el sistema ya prendido con esta nueva tarjeta SD se pueden dejar grabados los cambios que harán efecto en el modelo Zero.


1) Instalar Avahi soporte (zeroconf) 

   Se necesita instalar tanto en el Rpi como en el notebook el soporte para autodescubrimiento de dispositivos Zeroconf también conocido Bounjour. Esto aporta a que luego de configurado podamos conectarnos desde el notebook solo utilizando el nombre del Rpi, no teniendo que conocer la ip de éste.
   Lo mínimo para que funcione es instalar avahi-daemon que permite interconectarse por este protocolo. Adicionalmente instalamos avahi-discover que presenta herramientas y e interfaz gráfica para ayudar a dilucidar la dirección IP del aparato.

$ sudo apt-get update && sudo apt-get upgrade
$ sudo apt-get install avahi-daemon avahi-discover

   En mi caso el mismo comando me sirve en ambos ambientes, pues tanto Raspbian como Linux Mint estan basados en la distribución Debian.

2) Anotar o ajustar el nombre de host del RPi Zero

Este paso es para evitarse problemas después, pues en este momento al estar conectado en la consola del Zero es fácil buscar el nombre de host del aparato, para no tener que adivinarlo después. Se puede usar este comando:

$ sudo cat /etc/hostname 
rp04
  Si la tarjeta está recién creada lo más probable es que se llame raspberrypi (cual es el nombre por defecto de una nueva imagen), pero si está clonada desde otro RPi (por ejemplo utilizando la nueva utilidad para clonar la tarjeta SD incorporado en versiones recientes de Raspbian), es probable que tenga otro nombre, como en mi caso es rpi04.  En cualquiera de los casos hay que anotar este nombre para usarlo posteriormente.

Si se desea cambiarlo, también es un buen momento, aunque hay que hacerlo en dos archivos: /etc/hostname y /etc/hosts.

En el primero simplemente es llegar y escribir otro:
$ sudo nano /etc/hostname
 En este otro hay que buscar la línea que comience con 127.0.1.1 y cambiar el nombre por el de su gusto, siempre que entre ambos archivos mantengan el mismo nombre.
$ sudo nano /etc/hosts
en mi caso quise llamar a mi RPi Zero como rpi04, por lo que la línea me quedó así:
127.0.1.1                rpi04

3) Habilitar el modo OTG en el Zero

   Este es el paso crítico en el que hay tener cuidado con las instrucciones y respaldar la configuración anterior para que pueda iniciar bien el Zero y no tener que hacer cosas complicadas para recuperación.

3a) Editar config.txt

   Se tiene que editar dos archivos de la partición /boot que son los importantes al iniciar éste. El primero es config.txt, que define los parámetros que usa el videocore, que es el coprocesador que bootea primero y carga luego el kernel de linux en el ARM.

$ cd /boot
$ cp config.txt config.txt.bak
$ sudo nano config.txt
  En este último archivo tenemos que agregar al final la linea siguiente, grabando el archivo y cerrándolo:
dtoverlay=dwc2
   Esto le indica que se tiene que cargar el archivo de overlay correspondiente que prepara los módulos a cargar en el kernel.

3b) Editar cmdline.txt

   Luego del mismo directorio /boot hay que editar el archivo cmdline.txt, con las siguientes precauciones: tener harto cuidado de mantener un solo carácter de espacio entre palabras y no usar el enter, pues todo debe quedar en una sola línea. Por si sale algo mal, partimos haciendo un respaldo del archivo actual y luego editamos el archivo.

$ sudo cp cmdline.txt cmdline.txt.bak
$ sudo nano cmdline.txt
   Dentro de este archivo tenemos que buscar donde aparece el comando rootwait y a continuación de ello agregar modules-load=dwc2,g_ether, teniendo cuidado si había más instrucciones luego de rootwait, no borrarlas.

   En mi caso el archivo antes de la inserción tenía lo siguiente:
dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p7
rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
   Y así quedó luego de insertar el comando para la carga del módulo del kernel:

dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p7
rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait modules-load=dwc2,g_ether
   Este parámetro indica que se debe cargar el módulo del kernel dwc2, exponiendo la interfaz ethernet hacia afuera.

En el caso que la tarjeta SD estuviera recién generada, lo mas probable es que el contenido sea algo como esto:
dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p7 
rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait 
quiet init=/usr/lib/raspi-config/init_resize.sh
por lo que debemos insertar el texto entre medio (con las precauciones de mantener un solo espacio y sin enter) dejándolo como:
dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p7 
rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait modules-load=dwc2,g_ether 
quiet init=/usr/lib/raspi-config/init_resize.sh
Todavía no hay que cerrar el archivo, pues es necesario aún...

3c) Agregar definición de direcciones de hardware y host

   Si guardáramos y cerráramos el documento tal como está, igual funcionaría la conexión, pero cada vez que se volviera a reconectar al notebook se produciría un efecto no deseado: el Zero aparece cada vez con una dirección de red diferente en IP6, y sin dirección IP4.
   Esto ocurre por una cadena de hechos, partiendo por como funciona el driver DWC2 por defecto: si no le indican una dirección de hardware (conocida por MAC address antiguamente) ni la que debe asumir la interfaz de red USB visible para ambas partes, ocurre que el drivers genera ambas al azar cada vez que se prende el Zero. Así no se puede predeterminar una asignación de IP (que podría hacerse en el archivo /etc/network/interfaces) o que el notebook le comparta internet al Zero. No es tan loca la situación, considerando que se no hay un dispositivo físico de red que venga de fábrica con las direcciones físicas ya predeterminadas

  Por ello al menos necesitamos asignar los parámetros host_addr y dev_addr en este mismo archivo con el siguiente téxto, teniendo el cuidado de mantener un solo espacio y sin enter al final:

g_ether.dev_addr=be:eb:df:c0:95:6e g_ether.host_addr=1A:B8:86:9D:80:61

   Las direcciones be:eb:df:c0:95:6e y 1A:B8:86:9D:80:61 son de ejemplo, aunque si se les pone esas, igual funcionaría. En mi caso yo utilicé las direcciones que me generó el Zero la primera vez que lo prendí sin esos parámetros, detectándolas y anotándolas, usando el camino difícil, tal como en la guía de Andrew Mulholland (pues la falta de estos fue detectada y reportada por las personas en los comentarios al post). Para que a usted no lo pase, lo agregué como un punto más. Así, el texto quedaría como:

dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p7 
rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait modules-load=dwc2,g_ether
g_ether.dev_addr=be:eb:df:c0:95:6e g_ether.host_addr=1A:B8:86:9D:80:61
Para el caso de una tarjeta SD recién generada el texto quedaría como:
dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p7 
rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait modules-load=dwc2,g_ether
g_ether.dev_addr=be:eb:df:c0:95:6e g_ether.host_addr=1A:B8:86:9D:80:61 
quiet init=/usr/lib/raspi-config/init_resize.sh
  En cualquiera de los casos no hay que olvidar guardar los cambios y cerrar el archivo.

4) Apagar el RPi Zero, y conectar al notebook

 Ahora toca apagar el aparatito:
$ sudo shutdown now
Como el RPi Zero tiene un solo led, esta luz funciona diferente a los otros modelos, estando prendida al estar funcionando éste en idle y parpadeando al tener actividad, por lo que el indicio de estar realmente apagado es que el led se apague. Lógico, ¿no? ;-)

  Ahí se puede desconectar el Zero de todas sus ataduras (jaja) y podemos conectarlo minimalistamente con un cable normal USB a micro USB al PC.

Alternativa sin usar el Raspberry Zero:

   Como mencioné al comienzo, si se está usando otro modelo de Raspberry para preparar la tarjeta SD con los cambios, luego de terminado este paso se debe sacar la tarjeta del RPi anfitrión y ponerla en el RPi Zero, pues ya le llegó su hora de entrar en acción.

Conectar el RPi Zero al notebook

   Al conectarse en ambos extremos el cable USB, se prenderá el led del RPi Zero comenzando a parpadear, mostrando el avance en el proceso de inicialización. Sólo cuando termine completamente el parpadeo (hasta 90 segundos después en la primera inicialización) podemos estar seguros de que terminó y que podemos comenzar el paso siguiente, pues se requiere que el RPi haya levantado la interfaz OTG y detectado al notebook.
   Si la tarjeta estaba recién creada, incluso es posible que el RPi se rebootee solo (por la tarea de que agranda el espacio de una tarjeta SD si esta es mas grande de 4GB que mostraba el segundo caso del punto anterior) y comience otra vez el proceso de inicializarse. En mi caso me dí cuenta por el notebook que la interfaz de red que se ve en el paso siguiente se conectó, desconecto y luego se volvió a conectar otra vez.

5) Detectar la conexión de red por USB 

   Luego de prenderse e inicializarse el Zero, en mi notebook se detectó una nueva conexión de red. Al ver el detalle, me aparece que el Notebook tiene una red llamada "Conexión Cableada 1" con MAC Address 1A:B8:86:9D:80:61.

   Esto me pareció raro, pues esperaba que apareciera el contenido del parámetro dev_addr y no el del host_addr. Sin embargo, entendí que lo entregado a dev_addr nombra a la interfaz de red virtual del RPi Zero, y lo entregado a host_addr nombra a la interfaz de red virtual en el Notebook.

Intento de conexión al RPi Zero por nombre


   En fin, viendo que ya aparece la conexión de red intenté conectarme al RPi Zero conociendo su nombre desde lo anotado del paso 2), con el comando SSH, pero no funcionaba:
$ ssh pi@rpi04.local
ssh: Could not resolve hostname rpi04.local: Name or service not known

   Luego utilizando una de las utilidades de Avahi en el notebook, se pueden ver las IP y dispositivos detectados, con el comando siguiente:

$ avahi-browse -art
+  wlan0 IPv6 lingatunoNtb [a0:a8:cf:98:ac:48]   Workstation            local
+  wlan0 IPv4 lingatunoNtb [a0:a8:cf:98:ac:48]   Workstation            local

+   usb0 IPv6 rpi04 [be:eb:df:c0:95:6e]          Workstation            local

+   usb0 IPv6 lingatunoNtb [1a:b8:86:9d:80:61]   Workstation            local
+  wlan0 IPv6 lingatunoNtb                       Remote Disk Management local
+  wlan0 IPv4 lingatunoNtb                       Remote Disk Management local

+   usb0 IPv6 rpi04                              Remote Disk Management local
+   usb0 IPv6 lingatunoNtb                       Remote Disk Management local
=   usb0 IPv6 rpi04 [be:eb:df:c0:95:6e]          Workstation            local
   hostname = [rpi04.local]
   address = [fe80::34ba:41aa:bcac:a620]
   port = [9]
   txt = []

=   usb0 IPv6 lingatunoNtb [1a:b8:86:9d:80:61]   Workstation            local
   hostname = [lingatunoNtb.local]
   address = [fe80::18b8:86ff:fe9d:8061]
   port = [9]
   txt = []

=   usb0 IPv6 rpi04                              Remote Disk Management local
   hostname = [rpi04.local]
   address = [fe80::34ba:41aa:bcac:a620]
   port = [22]
   txt = []


   Se pueden reconocer los siguientes grupos:
  • Lo marcado en amarillo se corresponde a la interfaz virtual como la ve el RPi Zero, puesto que se comunica a través de la interfaz usb0 que es la que activamos en las configuraciones del punto 3). Como se ve, solamente presenta dirección IPv6 (fe80::18b8:86ff:fe9d:8061).
  • Lo marcado en cyan corresponde a la interfaz virtual como la expone el notebook, comunicada por el otro extremo de la interfaz usb0, con una dirección IPv6 diferente (fe80::34ba:41aa:bcac:a620). 
  • Por último, lo marcado en blanco es la interfaz Wifi del notebook, que tiene tanto dirección IPv4 como IP6.

Conexión al RPi Zero usando IP6

Considerando lo anterior, el notebook no puede llegar al RPi, primero por que sólo tiene dirección IP6 y no tiene IPv4, y el comando ssh utiliza por defecto esta última dirección. Si utilizo ssh -6 para especificar que use IPv6, sí si se llega, pues sí hay visibilidad usando esta dirección, pero se debe especificar esta en forma completa (se pone bastante feo el comando):
$ ssh pi@fe80::34ba:41aa:bcac:a620%usb0
The authenticity of host 'fe80::34ba:41aa:bcac:a620%usb0 (fe80::34ba:41aa:bcac:a620%usb0)' 
can't be established.
ECDSA key fingerprint is 03:a8:c4:0a:c0:ed:de:ab:7d:63:e4:da:21:b1:16:d5.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'fe80::34ba:41aa:bcac:a620%usb0' (ECDSA) to the list of known hosts.
pi@fe80::34ba:41aa:bcac:a620%usb0's password:

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Wed May 25 01:17:17 2016
pi@rpi04:~ 

   Donde pi@ especifica que se conecte usando el login de usuario pi, fe80::34ba:41aa:bcac:a620 es la dirección IPv6 y %usb0 es la interfaz de red que se utiliza.
   Funciona pero no es para nada cómodo, al tener que buscarse la dirección IPv6 cada vez, la que podría variar mas encima.

Confirmación: Detalles de interfaz usb0 en el RPi Zero

Estando al fin conectado a la consola del RPi Zero desde el notebook, podemos ver los detalles de la interfaz usb0 desde la perspectiva del RPi, lo que reafirma el que no tiene dirección IPv4 definida:
$ ifconfig usb0
usb0      Link encap:Ethernet  direcciónHW be:eb:df:c0:95:6e 
          Dirección inet6: fe80::34ba:41aa:bcac:a620/64 Alcance:Enlace
          ACTIVO DIFUSIÓN FUNCIONANDO MULTICAST  MTU:1500  Métrica:1
          Paquetes RX:325 errores:0 perdidos:0 overruns:0 frame:0
          Paquetes TX:1009 errores:0 perdidos:0 overruns:0 carrier:0
          colisiones:0 long.colaTX:1000
          Bytes RX:62812 (62.8 KB)  TX bytes:221885 (221.8 KB)

Confirmación: Información desde log del kernel del módulo DWC2

 Podemos aprovechar de revisar en el Zero el log del módulo DWC2, viendo que los parámetros host_addr y dev_addr fueron considerados, mirando el resultado del comando DMESG (copié aquí sólo las partes relevantes al módulo):

 $ dmesg -H
...
[  +0,245730] dwc2 20980000.usb: DWC OTG Controller
[  +0,007742] dwc2 20980000.usb: new USB bus registered, assigned bus number 1
[  +0,032035] dwc2 20980000.usb: irq 33, io mem 0x00000000
[  +0,026420] usb usb1: New USB device found, idVendor=1d6b, idProduct=0002
[  +0,009900] usb usb1: New USB device strings: Mfr=3, Product=2, SerialNumber=1
[  +0,010223] usb usb1: Product: DWC OTG Controller
[  +0,007660] usb usb1: Manufacturer: Linux 4.4.9+ dwc2_hsotg
[  +0,008527] usb usb1: SerialNumber: 20980000.usb
[  +0,022201] systemd-udevd[108]: starting version 215
[  +0,185440] hub 1-0:1.0: USB hub found
[  +0,000112] hub 1-0:1.0: 1 port detected
[  +0,172219] using random self ethernet address
[  +0,007306] using random host ethernet address
[  +0,006995] using host ethernet address: 1A:B8:86:9D:80:61
[  +0,005494] using self ethernet address: be:eb:df:c0:95:6e
[  +0,091771] usb0: HOST MAC 1a:b8:86:9d:80:61
[  +0,045861] usb0: MAC be:eb:df:c0:95:6e

[  +0,006638] using random self ethernet address
[  +0,007178] using random host ethernet address
[  +0,076208] g_ether gadget: Ethernet Gadget, version: Memorial Day 2008
[  +0,009344] g_ether gadget: g_ether ready
[  +0,006707] dwc2 20980000.usb: dwc2_hsotg_enqueue_setup: failed queue (-11)
[  +0,012686] dwc2 20980000.usb: bound driver g_ether
...
[jul13 01:22] Adding 102396k swap on /var/swap.  Priority:-1 extents:1 across:102396k SSFS
[  +0,050907] IPv6: ADDRCONF(NETDEV_UP): usb0: link is not ready
[  +2,060551] dwc2 20980000.usb: new device is high-speed
[  +0,184062] dwc2 20980000.usb: new device is high-speed
[  +0,112111] dwc2 20980000.usb: new device is high-speed
[  +0,056973] dwc2 20980000.usb: new address 16
[  +0,017481] g_ether gadget: high-speed config #1: CDC Ethernet (ECM)
[  +0,000744] IPv6: ADDRCONF(NETDEV_CHANGE): usb0: link becomes ready


 Lo marcado en cyan muestra primero el mensaje que se usarán valores al azar para los parámetros, sin embargo como le especificamos valores definidos, lo que está en verde demuestra que sí fueron considerados.

6) Activar asignación de IPv4

   Para mejorar esto, la idea es hacer que el notebook siempre le asocie la misma.   Así hay que cambiar el seteo en el notebook para que se le asigne una dirección de red a la dirección MAC del la red virtual usb0. Para ello en linux mint hay que abrir "Conexiones de Red":

Allí en la pestaña "Ajustes de IPv4", hay que cambiar el valor para el campo Método, en vez de "Manual" a "Solo enlace local".

   Luego hay que apretar el botón Guardar.


7) Compartir acceso a internet al RPi desde el notebook con Linux Mint

  El verdadero truco es cambiar en ese mismo cuadro de diálogo por el valor "Compartida con otros equipos", que aparte le comparte el acceso a Internet que tenga el notebook al RPi Zero.

   Ahora podemos probar conectarse directamente al RPi Zero usando el nombre:

$ sudo ssh pi@rpi04.local
pi@rpi04.local's password:

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Wed May 25 01:17:17 2016
pi@rpi04:~ $ 

Y así finalmente tener el acceso a Internet compartido desde el notebook. En el ejemplo, actualizando los paquetes instalados: