NuttX: Usando o Simulador para Testes e Debugging de Aplicações

Este post faz parte da série Primeiros Passos com o ESP32 e NuttX

Este artigo faz parte da série Primeiros Passos com o ESP32 e NuttX e mostra como o simulador do NuttX é uma importante ferramenta de apoio para o desenvolvimento, depuração e validação de aplicações no NuttX. Para tal, este artigo faz referência ao conteúdo do artigo NuttX: Criando (ou Copiando!) uma Aplicação para o NuttX e aborda as etapas subsequentes para desenvolver um projeto usando o NuttX a partir de uma aplicação já existente.

O artigo NuttX: Criando (ou Copiando!) uma Aplicação para o NuttX termina com um teste de build do NuttX que inclui a aplicação RTP Tools que foi portada para o NuttX. Para tal, bastaria executar:

make distclean
./tools/configure.sh sim:rtptools
make -j

Obviamente, este teste funcionaria para qualquer usuário que executasse esses comandos hoje, no NuttX, mas não foi tão simples assim quando o RTP Tools foi portado para o NuttX. Veremos, neste artigo, as alterações necessárias no sistema operacional que possibilitaram o uso do RTP Tools como pensado para a construção da aplicação final.

Obviamente, este teste funcionaria para qualquer usuário que executasse esses comandos hoje, no NuttX, mas não foi tão simples assim quando o RTP Tools foi portado para o NuttX. Veremos, neste artigo, as alterações necessárias no sistema operacional que possibilitaram o uso do RTP Tools como pensado para a construção da aplicação final.

A Jornada do Desenvolvimento

Este artigo vai começar a explorar o uso do RTP Tools em uma aplicação real com o NuttX. Esta aplicação – uma placa de som externa que recebe o áudio através da rede – poderia integrar, facilmente, um produto.

Antes de propriamente explorar este desenvolvimento, precisaremos entender como o RTP Tools fornece um meio de recebimento de pacotes de áudio pela rede e, também, como estes pacotes podem ser enviados a partir de aplicações disponíveis em um computador pessoal. Para tal, vamos utilizar o Linux como ponto de partida para compilar o RTP Tools e, também, para mostrar como o redirecionamento do áudio pode ser realizado para a rede (calma, isso tudo pode ser feito com o Windows também!). A seguir, será explorada a execução do RTP Tools no NuttX e como o simulador é capaz de emular a aplicação que recebe pacotes de áudio pela rede.

Ainda utilizando o simulador do NuttX, veremos como depurar ( ou “debugar”) uma aplicação com o GDB (GNU Debugger) para encontrar erros de execução. Finalmente, será mostrado técnicas de depuração que nos permitem chegar a raíz de um problema e, assim, resolvê-lo. Vamos lá?

Compilando o RTP Tools no Linux

De modo a “aprender” um pouco mais sobre o RTP Tools e suas aplicações e, para tal, iremos compilá-lo novamente para o Linux (já o fizemos no artigo anterior NuttX: Criando (ou Copiando!) uma Aplicação para o NuttX). Na pasta do repositório do RTP Tools:

$ ./configure
$ make -j
$ sudo make install

Estes comandos irão, respectivamente, configurar o RTP Tools para compilação, compilá-lo e instalá-lo no Linux. Uma vez instalado, podemos verificar o funcionamento das aplicações do RTP Tools como, por exemplo, do rtpdump. Para tal, basta executar man rtpdump.

O rtpdump

De acordo com a descrição do manual do rtpdump:

rtpdump reads RTP and RTCP packets on the given address/port, or from standard input by default, and dumps a processed version to standard output.  The output is suitable as input for rtpplay(1) and rtpsend(1).  The IPv4 address can be specified in dotted decimal notation or as a hostname and defaults to “localhost”.  The port number must be an even number.

Embora ainda não tenha sido falado sobre a motivação para o projeto que necessita do RTP Tools (e, em particular, do rtpdump), esta será a aplicação responsável por receber pacotes RTP pela rede. Estes pacotes, por sua vez, podem ser gerados pelo pulseaudio, que é um servidor de som para sistemas operacionais POSIX.

Transmitindo Pacotes RTP

Para testar o funcionamento do rtpdump no Linux, precisamos, primeiramente transmitir pacotes RTP (Real-time Transport Protocol) que, basicamente, é um protocolo de rede para entrega de pacotes de áudio, como Voz Sobre IP. Para este artigo, iremos explorar duas opções para gerar streams RTP:

PulseAudio

O PulseAudio é, geralmente, a solução padrão para o gerenciamento de áudio em diversos sistemas operacionais Linux. É, por exemplo, a solução padrão para o Ubuntu. O PulseAudio permite redirecionar o áudio do computador para um stream RTP. Para tal, abra um terminal e execute os seguintes comandos:

$ pactl load-module module-null-sink sink_name=rtp format=s16le channels=2 rate=44100 sink_properties="device.description='RTP_sink'"
$ pactl load-module module-rtp-send source=rtp.monitor format=s16le destination_ip=127.0.0.1 port=46998 inhibit_auto_suspend=always

Brevemente, o primeiro comando cria um sink do tipo null do PulseAudio e o segundo usa o monitor deste sink para enviar pacotes RTP para um endereço de rede, no caso o próprio computador.

pavucontrol

Uma aplicação bastante simples para configurar os sources e sinks, além das placas de som disponíveis no sistema, no PulseAudio é o pavucontrol. Procure-o no gerenciador de pacotes de sua distribuição!

Ao executar os comandos acima para criar um sink do tipo null, podemos verificar, na aba Output Devices:

O ícone ao lado do sink permite selecioná-lo como a saída de áudio padrão do sistema. A aba Playback permite selecionar, para cada fonte de áudio – como o browser, por exemplo – a saída de áudio utilizada:

Observe que, neste exemplo, o áudio do Google Chrome está sendo enviado ao RTP_sink que, por sua vez, encaminha os dados através de pacotes RTP pela rede.

VLC

Para transmitir áudio através da rede usando o protocolo RTP pelo VLC, selecione – no menu superio – Media -> Stream… e, na tela que será aberta, na aba File, clique em + Add e selecione o arquivo de áudio a ser transmitido (preferencialmente, um arquivo não comprimido no formato WAV). A seguir, clique em Stream mais abaixo na tela. Selecione Next e, em Destination Setup, selecione a opção RTP Audio/Video Profile na caixa de seleção New destination. Clique em Add. A tela que será aberta permite configurar o IP de destino que pode ser, por exemplo, `127.0.0.1`. A porta, `46998`. Clique em Next. Na tela Transcoding Options, desmarque a caixa Activate Transcoding e clique, novamente, em Next. Finalmente, clique em Stream para iniciar a transmissão.

Recebendo Pacotes RTP

Após configurar a transmissão de pacotes RTP, podemos executar a aplicação do RTP Tools que é capaz de receber estes pacotes da rede. Esta aplicação é o rtpdump. Para testá-lo, ainda no Linux:

$ rtpdump -F ascii /46998
1690643374.786189 RTP len=1280 from=127.0.0.1:43987 v=2 p=0 x=0 cc=0 m=0 pt=127 ((null),0,0) seq=20657 ts=2583521488 ssrc=0x5253a62d 
1690643374.794194 RTP len=1280 from=127.0.0.1:43987 v=2 p=0 x=0 cc=0 m=0 pt=127 ((null),0,0) seq=20658 ts=2583521805 ssrc=0x5253a62d 
1690643374.800515 RTP len=1280 from=127.0.0.1:43987 v=2 p=0 x=0 cc=0 m=0 pt=127 ((null),0,0) seq=20659 ts=2583522122 ssrc=0x5253a62d 
1690643374.807708 RTP len=1280 from=127.0.0.1:43987 v=2 p=0 x=0 cc=0 m=0 pt=127 ((null),0,0) seq=20660 ts=2583522439 ssrc=0x5253a62d 
1690643374.814981 RTP len=1280 from=127.0.0.1:43987 v=2 p=0 x=0 cc=0 m=0 pt=127 ((null),0,0) seq=20661 ts=2583522756 ssrc=0x5253a62d 
1690643374.822339 RTP len=1280 from=127.0.0.1:43987 v=2 p=0 x=0 cc=0 m=0 pt=127 ((null),0,0) seq=20662 ts=2583523073 ssrc=0x5253a62d 
1690643374.829418 RTP len=1280 from=127.0.0.1:43987 v=2 p=0 x=0 cc=0 m=0 pt=127 ((null),0,0) seq=20663 ts=2583523390 ssrc=0x5253a62d 
1690643374.836663 RTP len=1280 from=127.0.0.1:43987 v=2 p=0 x=0 cc=0 m=0 pt=127 ((null),0,0) seq=20664 ts=2583523707 ssrc=0x5253a62d

O comando `rtpdump -F ascii /46998` inicia o rtpdump no modo ascii, que imprime na tela algumas informações dos pacotes, e recebe os pacotes da porta 46998 (a mesma que foi configurada para enviar os pacotes RTP). O entendimento do funcionamento do RTP Tools (especificamente, o rtpdump) é fundamental para que possamos avaliar sua performance ao compilamos a mesma aplicação para o NuttX.

Verificando o Envio de Pacotes RTP pela Rede

De forma a verificar o envio de pacotes RTP pelo computador, podemos usar a aplicação Wireshark, que é capaz de monitorar pacotes de rede que trafegam pelas interfaces de rede do computador. Podemos, então, selecionar todas as interfaces do computador e filtrar para observarmos pacotes rtp. A imagem a seguir mostra os pacotes RTP sendo enviados pela rede (no caso, o destino é o próprio computador, através do endereço 127.0.0.1):

Compilando o RTP Tools pela Primeira Vez no NuttX

Considerando que no artigo anterior desta série o RTP Tools foi portado para o NuttX, vamos tentar agora compilá-lo através de uma configuração pré-existente que utiliza o simulador do NuttX como base.

Voltando no Tempo

Vamos considerar o repositório do NuttX tal como ele era quando portamos o NuttX pela primeira vez de modo que seja possível simular todas as etapas necessárias para ter o RTP Tools funcionando com sucesso. O NuttX, àquele momento, tinha sua branch master apontando ao commit f48693eaf5d3cec9161b796be91c7d6d24f09e91. Na pasta `nuttx` (referente ao repositório do sistema operacional e não ao repositório de aplicações), execute:

$ git checkout f48693eaf5d3cec9161b796be91c7d6d24f09e91

Como ainda não temos, neste repositório, uma defconfig específico para o RTP Tools, vamos partir do defconfig `sim:tcpblaster` e selecionar a aplicação `rtpdump` tal como fizemos na seção “1, 2, 3, Testando…” de NuttX: Criando (ou Copiando!) uma Aplicação para o NuttX: Replicando o procedimento já descrito anteriormente:

$ make -j distclean
$ ./tools/configure.sh -l sim:tcpblaster
$ make menuconfig

Pelo utilitário do `menuconfig`, navegue até: `Application Configuration → Network Utilities`. Nesta tela, selecione a opção `Enable RTP Tools`. Nas opções que serão descritas, entre em `RTP Tools Applications  —>` e selecione a opção `Enable rtpdump` para compilar a aplicação `rtpdump` do RTP Tools. Deixe as demais configurações com os valores padrão e saia do utilitário salvando as alterações realizadas.

Problemas durante o Build?

Uma vez selecionada a aplicação do RTP Tools através do `make menuconfig`, vamos tentar compilá-la pela primeira vez:

$ make -j

O resultado da execução deste comando será algo parecido com:

Create version.h
Downloading: littlefs/v2.5.1.tar.gz CP:  /home/tiago/Documents/tiago/projects/htpc/nuttxspace/nuttx/include/nuttx/config.h
CC:  tcpblaster_host.c
CC:  tcpblaster_cmdline.c
CC:  tcpblaster_server.c
Downloading: 9356740cb0c264577e0f9505e682e53a1e0996d5.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0LN: platform/board to /home/tiago/Documents/tiago/projects/htpc/nuttxspace/apps/platform/dummy
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0LD:  tcpserver
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100 59247    0 59247    0     0  96591      0 --:--:-- --:--:-- --:--:-- 96591
Unpacking: 9356740cb0c264577e0f9505e682e53a1e0996d5.tar.gz -> rtptools
Register: tcpclient
Register: ftpc
Register: tcpecho
Register: rtpdump
Register: ping
Register: nsh
Register: sh
Register: telnet
Register: ntpcstart
Register: ntpcstop
Register: ping6
Register: ntpcstatus
Register: telnetd
CP:  /home/tiago/Documents/tiago/projects/htpc/nuttxspace/nuttx/include/nuttx/config.h
CP:  /home/tiago/Documents/tiago/projects/htpc/nuttxspace/nuttx/include/nuttx/fs/hostfs.h
serial/ptmx.c: In function ‘ptmx_open’:
serial/ptmx.c:205:34: warning: ‘%d’ directive output may be truncated writing between 1 and 10 bytes into a region of size 8 [-Wformat-truncation=]
  205 |   snprintf(devname, 16, "/dev/pty%d", minor);
      |                                  ^~
serial/ptmx.c:205:25: note: directive argument in the range [0, 2147483647]
  205 |   snprintf(devname, 16, "/dev/pty%d", minor);
      |                         ^~~~~~~~~~~~
serial/ptmx.c:205:3: note: ‘snprintf’ output between 10 and 19 bytes into a destination of size 16
  205 |   snprintf(devname, 16, "/dev/pty%d", minor);
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rtptools/notify.c:47:5: error: ‘NSIG’ undeclared here (not in a function)
   47 | } s[NSIG];
      |     ^~~~
CC:  libm/lib_sinl.c rtptools/notify.c:47:3: warning: ‘s’ defined but not used [-Wunused-variable]
   47 | } s[NSIG];
      |   ^
CC:  netlib_setipv4dnsaddr.c make[3]: *** [/home/tiago/Documents/tiago/projects/htpc/nuttxspace/apps/Application.mk:171: notify.c.home.tiago.Documents.tiago.projects.htpc.nuttxspace.apps.netutils.rtptools.o] Error 1
make[3]: *** Waiting for unfinished jobs....
CC:  pthread/pthread_rwlock_wrlock.c make[2]: *** [Makefile:53: /home/tiago/Documents/tiago/projects/htpc/nuttxspace/apps/netutils/rtptools_all] Error 2
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [Makefile:47: all] Error 2
make: *** [tools/LibTargets.mk:232: /home/tiago/Documents/tiago/projects/htpc/nuttxspace/apps/libapps.a] Error 2
make: *** Waiting for unfinished jobs....

E, infelizmente, tivemos problemas durante a compilação :/ Vamos analisar em detalhes o problema:

rtptools/notify.c:47:5: error: ‘NSIG’ undeclared here (not in a function)
   47 | } s[NSIG];
      |     ^~~~
CC:  libm/lib_sinl.c rtptools/notify.c:47:3: warning: ‘s’ defined but not used [-Wunused-variable]
   47 | } s[NSIG];

Estas linhas indicam o erro: o arquivo `rtptools/notify.c` utiliza a macro `NSIG` que, aparentemente, não está sendo definida pelo NuttX. Vamos procurá-la nos arquivos do NuttX:

$ grep -rn ./ -e 'NSIG'
./fs/nfs/nfs_proto.h:117:#define NFSX_UNSIGNED             4
./fs/nfs/nfs_proto.h:126:#define NFSX_V3POSTOPATTR         (NFSX_V3FATTR + NFSX_UNSIGNED)
./fs/nfs/nfs_proto.h:127:#define NFSX_V3WCCDATA            (NFSX_V3POSTOPATTR + 7 * NFSX_UNSIGNED)
./include/signal.h:53:#define _NSIG           (MAX_SIGNO + 1) /* Biggest signal number + 1 */
./include/signal.h:59:#define _SIGSET_NELEM   ((_NSIG + 31) >> 5)
./include/signal.h:252:typedef struct sigset_s sigset_t; /* Bit set of _NSIG signals */

O arquivo de cabeçalho `include/signal.h` define a macro `_NSIG` (com o caractere `_` na frente), muito similar à macro `NSIG`. Uma rápida pesquisa na internet mostra que este macro é definida também em vários arquivos de cabeçalho `signal.h` do Linux como, por exemplo, em arch/arm/include/uapi/asm/signal.h. Especificamente, o arquivo arch/sparc/include/uapi/asm/signal.h a define como:

#define NSIG _NSIG

Como o NuttX já define `_NSIG`, podemos definir `NSIG` a partir de `_NSIG` incluindo, em `include/signal.h`

#define NSIG            _NSIG

Feito isso, vamos tentar compilar, novamente o NuttX com o RTP Tools:

$ make -j
Create version.h
serial/ptmx.c: In function ‘ptmx_open’:
serial/ptmx.c:205:34: warning: ‘%d’ directive output may be truncated writing between 1 and 10 bytes into a region of size 8 [-Wformat-truncation=]
  205 |   snprintf(devname, 16, "/dev/pty%d", minor);
      |                                  ^~
serial/ptmx.c:205:25: note: directive argument in the range [0, 2147483647]
  205 |   snprintf(devname, 16, "/dev/pty%d", minor);
      |                         ^~~~~~~~~~~~
serial/ptmx.c:205:3: note: ‘snprintf’ output between 10 and 19 bytes into a destination of size 16
  205 |   snprintf(devname, 16, "/dev/pty%d", minor);
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
LD:  nuttx

E temos, finalmente, o RTP Tools compilado para o NuttX! Precisaremos, é claro, submeter esta simples alteração referente ao `NSIG` para o NuttX (mas isso fica para o pŕoximo artigo).

Usando o Simulador do NuttX

O simulador do NuttX é uma ferramenta importantíssima de apoio ao desenvolvimento de aplicações e projeto utilizando o NuttX, eliminando, por vezes, a necessidade de ter um hardware real para o desenvolvimento de aplicações e facilitando o processo de depuração destas. Seu uso, entretanto, não é tão bem documentado além da própria documentação oficial do NuttX e, por isso, exploraremos melhor seu uso neste artigo com um exemplo de aplicação prática!

Uma Breve Introdução

O Simulador do NuttX, para todos os efeitos, é apresentado como uma placa (board) dentre as muitas placas suportadas. Da mesma forma, é vista como uma arquitetura (arch) única. Ou seja, ao invés de selecionar um hardware real para compilar o NuttX, podemos selecionar o simulador e compilá-lo da mesma forma. O arquivo de saída da compilação, ao invés de um firmware a ser gravado em uma placa, será uma aplicação que pode ser executada como um programa regular no Linux, Windows ou MacOS.

Considerando-se a documentação do NuttX, temos algumas páginas que documentam o simulador:

Vale destacar também alguns aspectos do simulador:

  • Ele não é uma máquina virtual, é tão somente uma aplicação que implementa uma versão do NuttX que roda em uma thread do sistema operacional do host;
    • Considerando um ambiente de simulação de uma plataforma multi-core, mais de uma thread é utilizada para tal;
  • É uma versão de “baixa fidelidade” ao sistema operacional do NuttX. Por exemplo, não existe suporte a qualquer evento assíncrono como interrupções e, consequentemente, preempção;
  • O simulador implementa, entretanto, um esquema de interrupções “fake” para a realização de troca de contexto, permitindo a simulação de aplicações do NuttX;
  • Considerando o suporte à emulação de diferentes periféricos e sub-sistemas do NuttX, a maioria dos recursos emulados estão disponíveis para o Linux (sim, é recomendável executar o simulador no Linux);

Como, essencialmente, é uma aplicação do espaço do usuário tal como outras aplicações para Linux, é possível utilizarmos as ferramentas de debugging/depuração do Linux ao executar a aplicação e, portanto, aqui se apresenta a grande vantagem do simulador do NuttX: é uma ferramenta extremamente útil para depurar aplicações do NuttX, principalmente aquelas que não dependem especificamente de driver de dispositivos como, por exemplo, aplicações baseadas em TCP/IP, servidores web etc.

Executando o Simulador

Uma vez que tenhamos compilado com sucesso o NuttX com o `rtpdump`, do RTP Tools, a partir da defconfig `sim:tcpblaster`, podemos executá-lo com `./nuttx`:

$ ./nuttx
NuttShell (NSH) NuttX-10.4.0
nsh>

Ao executar, no terminal do NuttX, o comando `help`:

nsh> help
help usage:  help [-v] [<cmd>]

    .         cat       echo      help      ls        printf    sleep     unset     
    [         cd        env       hexdump   mkdir     ps        source    uptime    
    ?         cp        exec      ifconfig  mkfifo    put       test      usleep    
    alias     cmp       exit      ifdown    mkrd      pwd       time      wget      
    unalias   dirname   false     ifup      mount     readlink  true      xd        
    arp       dd        fdinfo    kill      mv        rm        truncate  
    basename  df        free      losetup   nslookup  rmdir     uname     
    break     dmesg     get       ln        poweroff  set       umount    

Builtin Apps:
    ftpc        ntpcstatus  ping        tcpclient   telnetd     
    nsh         ntpcstop    rtpdump     tcpecho     
    ntpcstart   ping6       sh          telnet      
nsh>

E, então, lá temos o `rtpdump`. Antes de prosseguirmos com a execução deste comando, vamos verificar a capacidade do simulador de acessar a rede do computador host e, assim, interagir com a rede local e, até mesmo, de acessar a internet.

Acessando a Rede Usando o Simulador

A seção Accessing the Network do guia do Simulador mostra as etapas para acessar os recursos de rede do computador host através do simulador. Antes de executarmos os passos descritos, vamos sair do simulador. Para tal:

nsh> poweroff

Agora, podemos executar os seguintes comandos no terminal:

$ sudo setcap cap_net_admin+ep ./nuttx

Este comando configuração as permissões para a aplicação do simulador do NuttX acessar a rede do computador. A seguir, executamos novamente o simulador com o comando `./nuttx` e, dentro do simulador:

nsh> ifup eth0
ifup eth0...OK

A seguir, em outro terminal do computador, precisamos identificar uma interface de rede a ser “conectada” ao simulador do NuttX. Para tal, execute:

$ ifconfig
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 5846  bytes 614351 (614.3 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 5846  bytes 614351 (614.3 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

wlp0s20f3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.209  netmask 255.255.255.0  broadcast 192.168.1.25
        RX packets 219369  bytes 176416490 (176.4 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 108399  bytes 27213617 (27.2 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Neste caso, a interface `wlp0s20f3` é a interface de rede principal do sistema: observe a presença de um endereço de IP correspondente a uma rede local (192.168.1.209). Finalmente, vamos executar (ainda no terminal do computador host):

$ sudo ./tools/simhostroute.sh wlp0s20f3 on

Após a execução do comando, checamos as interfaces do computador host. Dentre as demais interfaces do computador, devemos enxergar a interface `nuttx0`, por exemplo:

$ ifconfig

nuttx0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 10.0.1.1  netmask 255.255.255.0  broadcast 0.0.0.0
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Podemos checar, então, se o simulador é capaz de acessar a rede do computador host: no terminal correspondente ao simulador, executamos:

nsh> ping -c 1 10.0.1.1
PING 10.0.1.1 56 bytes of data
56 bytes from 10.0.1.1: icmp_seq=0 time=0.0 ms
1 packets transmitted, 1 received, 0% packet loss, time 1010 ms
rtt min/avg/max/mdev = 0.000/0.000/0.000/0.000 ms

Ainda no simulador, podemos executar o comando `ifconfig` para verificar as interfaces de rede criados pelo simulador:

nsh> ifconfig
eth0	Link encap:Ethernet HWaddr 42:2e:14:be:cd:a1 at RUNNING
	inet addr:10.0.1.2 DRaddr:10.0.1.1 Mask:255.255.255.0
	inet6 addr: fc00::2/112
	inet6 DRaddr: fc00::1/112

	RX: Received Fragment Errors  
	    00000020 00000000 00000000
	    IPv4     IPv6     ARP      Dropped 
	    00000002 0000001c 00000002 00000000
	TX: Queued   Sent     Errors   Timeouts
	    00000004 00000004 00000000 00000000
	Total Errors: 00000000

lo	Link encap:Local Loopback at RUNNING
	inet addr:127.0.0.1 DRaddr:127.0.0.1 Mask:255.0.0.0
	inet6 addr: ::1/128
	inet6 DRaddr: ::1/128

	RX: Received Fragment Errors  
	    00000000 00000000 00000000
	    IPv4     IPv6     ARP      Dropped 
	    00000000 00000000 00000000 00000000
	TX: Queued   Sent     Errors   Timeouts
	    00000000 00000000 00000000 00000000
	Total Errors: 00000000

             IPv4  IPv6   TCP   UDP  ICMP  ICMPv6
Received     0002  001c  0000  000f  0002  000d
Dropped      0000  0000  0000  0000  0000  000d
  IPv4        VHL: 0000   Frg: 0000
  IPv6        VHL: 0000
  Checksum   0000  ----  0000  0000  ----  ----
  TCP         ACK: 0000   SYN: 0000
              RST: 0000  0000
  Type       0000  0000  ----  ----  0000  000c
Sent         0002  0000  0000  0000  0002  0000
  Rexmit  

Observe que o IP do simulador é 10.0.1.2. Precisaremos desta informação mais adiante.

RTP Tools no Simulador

É, enfim, chegada a hora de testar o rtpdump no NuttX. Certificando-se que 1) o simulador está com acesso à rede do computador e 2) que pacotes RTP estão sendo enviados para o endereço do simulador. Refira-se à seção Transmitindo Pacotes RTP para configurar a transmissão de pacotes RTP ao endereço do simulador: `10.0.1.2`.

Verificando o Envio de Pacotes ao Endereço do Simulador

Usando o software Wireshark, verificamos o envio de pacotes RTP ao endereço do simulador (10.0.1.2):

Executando o rtpdump no Simulador

Finalmente, podemos executar o rtpdump no simulador:

nsh> rtpdump -F short /46998
42949704.000000 710000 1235352047
42949704.000000 730000 1235352364
42949704.000000 730000 1235352681

Como podemos observar – e tal como no Linux – o rtpdump é capaz de receber pacotes RTP através do simulador do NuttX e, assim, acabamos de validar o port da aplicação para o NuttX!

Criando uma Configuração com o RTP Tools

Antes de prosseguirmos, vamos salvar a configuração que temos até o momento (ou seja, baseada na defconfig tcpblaster + RTP Tools). Para tal, execute no terminal do computador host, na pasta do repositório do NuttX:

$ make savedefconfig

O comando acima cria um arquivo, na raíz do repositório, denominado `defconfig` que contém as configurações selecionados pelo sistema de build do NuttX. Salvando-se este arquivo, podemos recarregá-lo sempre que quisermos selecionar os mesmos pacotes para serem compilados. Vamos salvá-lo, por exemplo, na pasta de configuração do simulador:

$ mkdir -p boards/sim/sim/sim/configs/rtptools
$ cp defconfig boards/sim/sim/sim/configs/rtptools/defconfig

Desta forma, sempre que quisermos recarregar esta configuração, basta executar `./tools/configure.sh sim:rtptools` (sim, foi o que fizemos no primeiro artigo da série).

Áudio no Simulador?

A demonstração anterior validou o funcionamento da aplicação rtpdump, do RTP Tools, para o NuttX. Porém, a forma que usamos o rtpdump – com o parâmetro `-F ascii` – tem pouca serventia em aplicações reais, sendo mais útil como validação da aplicação. Um exemplo de aplicação real, entretanto, seria extrair os dados de áudio – em formato “bruto” – enviados pelos pacotes RTP. Estes dados podem ser, então, redirecionados para o subsistema de áudio do NuttX.

Fica, entretanto, a pergunta: como testar esta aplicação de áudio usando o simulador? Ok, podemos receber pacotes RTP – contendo dados de áudio – mas como testar o redirecionamento destes pacotes para o subsistema de áudio do NuttX usando o simulador, sem a necessidade de utilizarmos uma placa de desenvolvimento com suportes à codecs de áudio (que, talvez, seja o objetivo final)?


Felizmente, o simulador do NuttX permite testar o subsistema de áudio através de uma ponte entre este e o sistemas operacionais hosts com suporte à ALSA (Advanced Linux Sound Architecture) que, como sugere o nome, é utilizado em sistemas Linux. Para testarmos o subsistema, isoladamente, vamos carregar a defconfig relacionada, executando os seguintes comandos na pasta do repositório do NuttX:

$ make -j distclean
$ ./tools/configure.sh -l sim:alsa

E, então, compilamos o simulador com esta configuração do simulador que é capaz de testar o subsistema de áudio do NuttX:

$ make -j

A seguir, podemos executar o simulador tal como anteriormente:

$ ./nuttx

Para testar o subsistema de áudio do NuttX com o simulador precisaremos, antes, de um arquivo de áudio não comprimido. Precisaremos saber, também, a taxa de amostragem, o número de canais e quantos bits/sample foram usados para a amostragem do áudio. O site freewavesamples.com fornece algumas amostras de áudio grátis e fornece, também, as informações sobre a amostragem. Baixe um destes exemplos e salve em uma pasta de seu computador. A configuração sim:alsa usa também o hostfs, que permite que uma pasta do computador host seja “montada” no simulador e que, assim, possamos acessar arquivos do sistema host no simulador. Vamos carregar a pasta que contém o arquivo de áudio que queremos testar no simulador:

nsh> mount -t hostfs -o fs=/caminho/da/pasta/com/arquivo/de/audio /host
nsh> ls /host

A seguir, vamos utilizar o nxplayer – o player de áudio do NuttX – para 1) selecionar o dispositivo de áudio a ser utilizado – no caso, o que faz a ponte com o sistema host através do ALSA – e 2) executar o arquivo de áudio do sistema host, que será acessado através do simulador:

nsh> nxplayer
NxPlayer version 1.05
h for commands, q to exit

nxplayer> device /dev/audio/pcm0p
nxplayer> playraw /host/mother.wav 2 16 44100

O device `/dev/audio/pcm0p` representa a placa de áudio para reprodução de áudio não comprimido (PCM). A seguir, selecionamos a reprodução do arquivo montado em `/host/` através do comando `playraw` que, além do caminho do arquivo no simulador, recebe como parâmetro o número de canais (`2`), a quantidade de bits/sample (`16`) e a taxa de amostragem (`44100`). A partir do momento que executarmos este último comando, devemos ouvir, no sistema host, a reprodução do arquivo de áudio. Usando o pavucontrol, por exemplo, vemos na aba Playback:

A entrada denominada `ALSA plug-in [nuttx]`, que está sendo reproduzida na placa de áudio padrão (`Built-in Audio Analog Stereo`), representa o áudio sendo reproduzido através do simulador do NuttX.

Feita esta introdução das capacidades do simulador do NuttX, partimos para um exemplo de aplicação que usa o RTP Tools e o subsistema de áudio do NuttX!

Debugging com o Simulador

Sabemos, até então, que o RTP Tools é capaz de receber pacotes de áudio transmitidos por pacotes RTP. De outro lado, sabemos que o NuttX possui um subsistema de áudio que é capaz de reproduzir arquivos de áudio não comprimidos. Como poderíamos, então, receber pacotes de áudio RTP e encaminhá-los para o subsistema de áudio do NuttX e termos, finalmente, um exemplo de aplicação real para o RTP Tools? E como testar tudo isso pelo simulador do NuttX?

Para encaminharmos os pacotes de áudio recebidos por RTP ao subsistema de áudio do NuttX (utilizando, por exemplo, o nxplayer), podemos usar um arquivo especial criado através do comando `mkfifo`. Este comando, que não é exclusivo do NuttX, faz parte do padrão POSIX e cria um arquivo que permite que dados sejam escritos e lidos de uma forma especial denominado FIFO (First-In First-Out). Uma FIFO é, basicamente, uma fila em que os dados escritos há mais tempo serão lidos primeiro. Este tipo de estrutura permite, por exemplo, a utilização de um arquivo FIFO para a troca de dados entre aplicações distintas, com uma aplicação escrevendo no arquivo e outra aplicação lendo dele.

A aplicação real será, então, uma “ponte” entre o rtpdump e o nxplayer. Respectivamente, o rtpdump receberá os pacotes RTP e escreverá os dados, em formato binário, em um arquivo FIFO. Este mesmo arquivo FIFO será aberto no nxplayer, que é a aplicação que permite reproduzir áudio no NuttX.


Partindo-se da defconfig que criamos (`rtptools`), vamos adicionar os pacotes necessários para acessar o subsistema de áudio do NuttX com o simulador.

Primeiro, vamos carregar o `defconfig` do RTP Tools

$ make -j distclean && ./tools/configure.sh sim:rtptools

A seguir, criamos um arquivo que compara as diferenças entre as configurações alsa e rtptools e gera uma lista das configurações que existem no primeiro (e não no segundo):

$ diff boards/sim/sim/sim/configs/rtptools/defconfig boards/sim/sim/sim/configs/alsa/defconfig | grep '^> ' | cut -c 3- > tmp.config

A seguir, adicionaremos estas diferenças às configurações do rtptools:

$ kconfig-merge -m .config tmp.config
$ make olddefconfig
$ make savedefconfig
$ cp defconfig boards/sim/sim/sim/configs/rtptools/defconfig

Finalmente, compilamos novamente o simulador com o comando `make`.

Teste da Aplicação Real

Antes de executar o simulador, vamos preparar o computador host para 1) acessar a rede do simulador e 2) enviar os pacotes RTP para o simulador (utilizaremos o PulseAudio, no exemplo):

$ sudo setcap cap_net_admin+ep ./nuttx
$ pactl load-module module-null-sink sink_name=rtp format=s16le channels=2 rate=44100 sink_properties="device.description='RTP_sink'"
$ pactl load-module module-rtp-send source=rtp.monitor format=s16le destination_ip=10.0.1.2 port=46998 inhibit_auto_suspend=always

A seguir, executando-se o simulador com o comando `./nuttx`, iremos 1) ligar a conexão de rede que permite a comunicação com o computador host e criar um arquivo especial FIFO chamado temp:

nsh> ifup eth0
nsh> mkfifo temp

Agora, no computador host, vamos ligar a rede do simulador à interface de rede do computador, tal como em Acessando a Rede Usando o Simulador:

$ sudo ./tools/simhostroute.sh wlp0s20f3 on

Podemos verificar, agora, que o simulador está recebendo os pacotes RTP do computador host:

nsh> rtpdump -F short /46998
42949704.000000 710000 1235352047
42949704.000000 730000 1235352364
42949704.000000 730000 1235352681

Uma vez validado o recebimento dos pacotes, podemos usar o comando `CTRL+C` para parar a execução do rtpdump. Agora, vamos executá-lo novamente gravando os dados (em formato binário) no arquivo FIFO que criamos anteriormente:

nsh> rtpdump -F payload -o temp /46998 &
rtpdump [5:100]
nsh> fwrite: Error 32

Houston, We Have a Problem!

Aparentemente, tivemos algum erro ao executar o rtpdump. Para confirmar, vamos executar o comando `ps` no simulador para verificar se, realmente, a aplicação não está sendo executada:

nsh> ps
  PID GROUP PRI POLICY   TYPE    NPX STATE    EVENT     SIGMASK           STACK   USED  FILLED COMMAND
    0     0   0 FIFO     Kthread N-- Ready              0000000000000000 069584 045072  64.7%  Idle Task
    1     1 255 FIFO     Kthread --- Waiting  Signal    0000000000000000 067496 001072   1.5%  loop_task
    2     2 224 FIFO     Kthread --- Waiting  Semaphore 0000000000000000 067496 000864   1.2%  hpwork 0x566a42b0
    3     3 100 FIFO     Kthread --- Waiting  Semaphore 0000000000000000 067496 001328   1.9%  lpwork 0x566a42c8
    4     4 100 FIFO     Task    --- Running            0000000000000000 067496 002688   3.9%  nsh_main

Realmente, o `rtptump` não está sendo executado como esperávamos. A partir deste ponto, precisamos entender o que acontece com a nossa aplicação. Existem, é claro, muitos métodos para depurar a aplicação. Poderíamos, por exemplo, ligar os logs no `make menuconfig` para obter maiores informações. Vamos, no entanto, usar o GDB para depurar a aplicação e tentar entender a origem do erro reportado.

Usando o GDB com o Simulador

O Simulador do NuttX permite não só avaliarmos as aplicações no NuttX sem a necessidade de um hardware real, mas também permite que executemos o GDB – a famosa ferramenta de depuração do GNU – para avaliar o código executa pelo simulador. Uma vez que o simulador do NuttX é executado como uma aplicação no sistema operacional host, o GDB pode ser utilizado tal como. O Nuttx provê scripts que permitem ao GDB entender o simulador como uma aplicação “standalone” (e não como uma aplicação qualquer do sistema operacional host). O documento Debugging with gdb, da documentação oficial, reporta a utilização do GDB para depuração do NuttX.

Vamos executar, então, o simulador do NuttX com o `gdb` e tentar entender o que está acontecendo com nossa aplicação. Para tal, execute no computador host:

$ sudo gdb nuttx -ix=tools/gdb/__init__.py --tui

O comando acima executa o GDB e acessa seu console interativo. A aplicação do simulador do NuttX ainda não está sendo executada. Antes de executá-la, vamos adicionar um breakpoint na aplicação do rtpdump. Lembramos, agora, que o ponto de entrada de cada aplicação no NuttX é a função `main()` e, portanto, a execução do rtpdump se inicia através da função `rtpdump_main`. No console do GDB, executamos:

```
(gdb) b rtpdump_main
Breakpoint 1 at 0x845c1: file rtptools/rtpdump.c, line 606.

A seguir, vamos executar a aplicação do simulador com o gdb. Para tal, usamos o comando `run`:

(gdb) run
...
NuttShell (NSH) NuttX-10.4.0
nsh> 

Observe que, agora, temos acesso ao terminal do NSH tal como quando executamos o simulador do NuttX anteriormente. Vamos, novamente, ligar a interface de rede, criar o arquivo FIFO e executar o rtpdump até que nosso breakpoint pause a execução do programa:

nsh> ifup eth0
ifup eth0...OK
nsh> ping -c 1 10.0.1.1
PING 10.0.1.1 56 bytes of data
56 bytes from 10.0.1.1: icmp_seq=0 time=0.0 ms
1 packets transmitted, 1 received, 0% packet loss, time 1010 ms
rtt min/avg/max/mdev = 0.000/0.000/0.000/0.000 ms
nsh> mkfifo temp
nsh> rtpdump -F payload -o temp /46998 &
rtpdump [6:100]
nsh> 
Thread 1 "nuttx" hit Breakpoint 1, rtpdump_main (argc=6, argv=0xdeadbeef) at rtptools/rtpdump.c:606
606	{
(gdb) 

Observe que o breakpoint foi atingido assim que executamos o comando `rtpdump -F payload -o temp /46998 &`: é a partir deste ponto que depuraremos a aplicação até descobrirmos a origem de nosso problema. O log da aplicação (de quando executamos o simulador ainda sem o GDB) nos mostrou que o erro aconteceu na função `fwrite`. Vamos tentar adicionar um breakpoint nesta função e continuar a execução, então:

(gdb) b fwrite
(gdb) c

A execução é pausada, novamente, ao atingirmos o breakpoint em `fwrite`. Podemos verificar, por exemplo, se esta função doi chamada pelo rtpdump através do comando `bt`:

(gdb) bt
#0  fwrite (ptr=0xf3cb8640, size=1268, n_items=1, stream=0xf3c970b0) at stdio/lib_fwrite.c:42
#1  0x565d56e0 in packet_handler (out=0xf3c970b0, format=F_payload, trunc=1000000, dstart=42949684.100000001, now=..., ctrl=0, sin=..., len=1280, 
    packet=0xf3cb862c) at rtptools/rtpdump.c:565
#2  0x565d60e0 in rtpdump_main (argc=6, argv=0xf3ca76c8) at rtptools/rtpdump.c:776
#3  0x565698fd in nxtask_startup (entrypt=0x565d590e <rtpdump_main>, argc=6, argv=0xf3ca76c8) at sched/task_startup.c:70
#4  0x5655e535 in nxtask_start () at task/task_start.c:134
#5  0x00000000 in ?? ()

O comando `bt` (backtrace) mostra as chamadas de funções que foram efetuadas antes de chegarmos ao ponto atual de execução do programa e, como podemos observar, a função `packet_handler` do rtpdump precedeu a chamada da função `fwrite`. A depuração, agora, deve seguir linha a linha até encontrarmos o ponto de falha que retorna o erro observado. Para executar o programa linha a linha, podemos usar o comando `next` (ou `n`) no GDB. Será necessário, também, entrar em funções aninhadas com o comando `step` (ou `s`) para identificar a origem do problema com a função `fwrite`.

Abordar em detalhes as técnicas de debugging foge um pouco do escopo deste artigo (deixe nos comentários, no entanto, se vocês querem que eu fale mais sobre isso em outro artigo). Vou pular algumas etapas de `next` e `step` que nos permitiu chegar até a função `pipecommon_write`. Vou deixar, porém, o backtrace até chegarmos a este ponto:

(gdb) bt
#0  pipecommon_write (filep=0xf3c96c50, buffer=0xf3cb8640 "", len=1268) at pipes/pipe_common.c:470
#1  0x5659a76a in file_write (filep=0xf3c96c50, buf=0xf3cb8640, nbytes=1268) at vfs/fs_write.c:89
#2  0x5659a7d8 in nx_write (fd=3, buf=0xf3cb8640, nbytes=1268) at vfs/fs_write.c:138
#3  0x5659a81d in write (fd=3, buf=0xf3cb8640, nbytes=1268) at vfs/fs_write.c:202
#4  0x565fa89a in lib_fwrite (ptr=0xf3cb8640, count=1268, stream=0xf3c970b0) at stdio/lib_libfwrite.c:138
#5  0x565fa6db in fwrite (ptr=0xf3cb8640, size=1268, n_items=1, stream=0xf3c970b0) at stdio/lib_fwrite.c:49
#6  0x565d56e0 in packet_handler (out=0xf3c970b0, format=F_payload, trunc=1000000, dstart=42950033.549999997, now=..., ctrl=0, sin=..., len=1280, 
    packet=0xf3cb862c) at rtptools/rtpdump.c:565
#7  0x565d60e0 in rtpdump_main (argc=6, argv=0xf3ca76c8) at rtptools/rtpdump.c:776
#8  0x565698fd in nxtask_startup (entrypt=0x565d590e <rtpdump_main>, argc=6, argv=0xf3ca76c8) at sched/task_startup.c:70
#9  0x5655e535 in nxtask_start () at task/task_start.c:134
#10 0x00000000 in ?? ()

Ao executarmos, linha a linha, a função `pipecommon_write`, chegamos até o ponto retratado abaixo:

Observe que, neste ponto, o valor da variável `nwritten` (verificado através do comando `p nwritten`) é igual a `0` e, segundo o código fonte, se o valor for `0`, será retornado o valor `-EPIPE` que, por sinal (como definido em `include/errno.h` é igual a `-32`). Encontramos, portanto, o local onde a função `fwrite` falha. Felizmente, o próprio código fonte contém um comentário que afirma que:


“REVISIT:  “If all file descriptors referring to the read end of a pipe have been closed, then a write will cause a SIGPIPE signal to be generated for the calling process.  If the calling process is ignoring this signal, then write(2) fails with the error EPIPE.”

Ou seja, quando não há leitor de um arquivo FIFO (ou pipe, como é aqui chamado), uma tentativa de escrita falhará com o erro EPIPE. Precisamos saber, entretanto, se este é o comportamento esperado de um arquivo FIFO segundo o padrão POSIX.

E a Compatibilidade com o Padrão POSIX?

Neste ponto, precisamos de um pouco de análise estática do código do rtpdump para entender o porquê da função `fwrite` estar falhando. Através do processo de debugging, notamos que `fwrite` chama a função `lib_fwrite` que, por sua vez, chama a função `write`, que é definida em `vfs/fs_write.c`. A função `write()` é uma das funções definidades pelo padrão POSIX. Ao consultar a documentação referente a esta função na definição do padrão, notamos que um dos código de retorno é, justamente, o EPIPE:

[EPIPE]

An attempt is made to write to a pipe or FIFO that is not open for reading by any process, or that only has one end open. A SIGPIPE signal shall also be sent to the thread.

Confirmamos, assim, que o comportamento da função é tal como o esperado pela definição POSIX.

Se o erro não está relacionado ao comportamento da função `fwrite`, onde estaria?
Bom, analisando o código do rtpdump, antes de escrever no arquivo FIFO, a aplicação precisa abri-lo para escrita. Isto é feito em `rtpdump.c`:

/* output file */
    case 'o':
      if (!(out = fopen(optarg, "wb"))) {
        perror(optarg);
        exit(1);
      }
      break;

Ao adicionarmos breakpoints na função `fopen`, podemos acompanhar a chamada de funções que acontecem a partir deste ponto:

A função fopen chama a função lib_mode2oflags com os argumentos wb (confirme indicado na chamada de fopen em rtpdump.c). Esta função seleciona as flags (que serão usadas, mais adiante, na função open()) O_WROK, O_CREAT O_TRUNC:

Adentramos, com o comando step do GDB, na função open que, seguindo-se o fluxo de chamada de funções aninhadas, chama a função pipecommon_open a partir de file_vopen:

A função pipecommon_open é, de fato, a função que abre o arquivo especial FIFO. Podemos verificar que ela é executada sem erros, retornando à função file_vopen.

Esse comportamento, no entanto, é o esperado segundo o padrão POSIX?

Consultamos, então, a definição do padrão para a função `open()` que, em certo momento, define o comportamento quando a função tenta abrir uma FIFO:
When opening a FIFO with O_RDONLY or O_WRONLY set:

If O_NONBLOCK is set, an open() for reading-only shall return without delay. An open() for writing-only shall return an error if no process currently has the file open for reading.

If O_NONBLOCK is clear, an open() for reading-only shall block the calling thread until a thread opens the file for writing. An open() for writing-only shall block the calling thread until a thread opens the file for reading.

Recordamo-nos, novamente, das flags utilizadas para abrir a FIFO pelo rtpdump: `O_WROK`, `O_CREAT` e  `O_TRUNC` foram selecionadas. A flag `O_WROK` é equivalente (de acordo com o arquivo de cabeçalho `nuttx/include/fcntl.h`) à flag `O_WRONLY` e, portanto, a função `open` deveria bloquear a thread que a chama até que outra thread abra o arquivo FIFO para leitura, de acordo com a norma.

Chegamos, então, a uma hipótese do que possa estar acontecendo com nossa aplicação: ao abrir um arquivo do tipo FIFO (com permissões de somente escrita) sem que outra thread o tenha aberto com permissão de leitura, a thread deveria bloquear a execução até que esta condição seja satisfeita. Uma vez satisfeita, a função de escrita no arquivo não falharia porque, agora, ele estaria aberto e apto a ler os dados que estariam sendo escritos. Ou seja, a aplicação do rtpdump falha porque, neste quesito, o NuttX não está seguindo a norma definida no padrão POSIX sobre o comportamento da função `open` sob tais circunstâncias.

Compatibilidade POSIX

O NuttX é um sistema em constante evolução e, portanto, sujeito a comportamentos que fogem do padrão POSIX, seja porque não foram ainda implementadas ou porque foram parcialmente implementadas. Felizmente, a utilização do simulador do NuttX com o GDB nos permitiu identificar a raíz do problema sem que fosse necessária a utilização do sistema em um hardware real.

Passamos, agora, à proposta para resolução do problema que é, basicamente, tornar o NuttX compatível com o padrão POSIX em relação à função `open` quando este for um arquivo do tipo FIFO. Observe que a função `pipecommon_open` (definida em `nuttx/drivers/pipes/pipe_common.c`) já implementa o bloqueio de abertura do arquivo FIFO para quando ele for aberto com permissões de somente leitura sem que haja ao menos uma thread que o abra com permissões de escrita:

while ((filep->f_oflags & O_NONBLOCK) == 0 &&    /* Blocking */
         (filep->f_oflags & O_RDWR) == O_RDONLY && /* Read-only */
         dev->d_nwriters < 1 &&                    /* No writers on the pipe */
         dev->d_wrndx == dev->d_rdndx)             /* Buffer is empty */
    {
      /* If opened for read-only, then wait for either (1) at least one
       * writer on the pipe (policy == 0), or (2) until there is buffered
       * data to be read (policy == 1).
       */

      nxmutex_unlock(&dev->d_bflock);

      /* NOTE: d_rdsem is normally used when the read logic waits for more
       * data to be written.  But until the first writer has opened the
       * pipe, the meaning is different: it is used prevent O_RDONLY open
       * calls from returning until there is at least one writer on the pipe.
       * This is required both by spec and also because it prevents
       * subsequent read() calls from returning end-of-file because there is
       * no writer on the pipe.
       */

      ret = nxsem_wait(&dev->d_rdsem);

A incompatibilidade que encontramos ocorre quando o arquivo é aberto como somente escrita sem que outra thread o tenha aberto com permissões de leitura e, portanto, precisaríamos implementar o mesmo tipo de checagem para esta situação. Esta solução foi implementada e subemtida para avaliação da comunidade do NuttX em https://github.com/apache/nuttx/pull/8985 (trataremos, em outro artigo, sobre como contribuir com o projeto do NuttX).

Teste_da_Aplicação_Real_v2

Uma vez solucionado o problema que encontramos ao testarmos o rtpdump pela primeira vez, podemos seguir novamente com o teste da aplicação, agora usando a branch master do repositório do NuttX (que já contém o código da solução). Para tal, vamos recompilar o simulador:

$ git checkout master
$ git pull origin master
$ make -j distclean && ./tools/configure.sh sim:rtptools && make -j
$ sudo setcap cap_net_admin+ep ./nuttx
$ ./nuttx

Em outro terminal, associamos o simulador com a interface de rede do computador host, tal como em Acessando a Rede Usando o Simulador:

$ sudo ./tools/simhostroute.sh wlp0s20f3 on

Ainda no mesmo terminal, configure o encaminhamento do áudio do computador para que seja enviado ao simulador através do protocolo RTP, tal como em PulseAudio, por exemplo.


Agora, voltado ao simulador do NuttX, podemos executar:

nsh> ifup eth0
nsh> mkfifo temp
nsh> rtpdump -F payload -o temp /46998 &

Observe que, agora, não temos mais qualquer mensagem de erro no terminal do NuttX. Vamos verificar se o `rtpdump` está mesmo executando em uma thread com o comando `ps`:

nsh> ps
  PID GROUP PRI POLICY   TYPE    NPX STATE    EVENT     SIGMASK           STACK   USED  FILLED COMMAND
    0     0   0 FIFO     Kthread N-- Ready              0000000000000000 069600 045072  64.7%  Idle_Task
    1     1 224 FIFO     Kthread --- Waiting  Signal    0000000000000000 067520 001104   1.6%  loop_task
    2     2 224 FIFO     Kthread --- Waiting  Semaphore 0000000000000000 067512 000992   1.4%  hpwork 0x566f9da0
    3     3 100 FIFO     Kthread --- Waiting  Semaphore 0000000000000000 067520 001328   1.9%  lpwork 0x566f9db8
    4     4 100 FIFO     Task    --- Running            0000000000000000 067512 002720   4.0%  nsh_main
    6     6 100 FIFO     Task    --- Waiting  Semaphore 0000000000000000 077728 009920  12.7%  rtpdump -F payload -o temp /46998

Como esperado, o rtpdump está executando em background! Podemos agora abrir o nxplayer e executar o arquivo de áudio FIFO que contém os dados dos pacotes de áudio sendo enviados pelo computador host ao simulador do NuttX:

nsh> nxplayer
nxplayer> device /dev/audio/pcm0p
nxplayer> playraw temp 2 16 44100

A partir deste momento, podemos verificar que o áudio do simulador está sendo encaminhado de volta ao computador pelo adaptador ALSA (isso, claro, considerando que esteja executando em um sistema Linux com pulseaudio). Podemos verificar novamente o pavucontrol, na aba Playback:

Neste exemplo, o som sendo produzido pelo Google Chrome está sendo encaminhado ao sink chamado RTP_sink que, por sua vez, envia os dados via RTP para o simulador do NuttX. Após recebermos os pacotes de áudio pelo rtpdump no simulador, o encaminhamos ao nxplayer que, finalmente, o reproduz através do adaptador ALSA, voltando novamente ao computador host.

Embora pareça “redundante” – já que estamos enviando áudio do computador host para o computador host (passando, é claro, pelo simulador do NuttX) – este exemplo valida a utilização do NuttX para a nossa aplicação real! Pudemos validar a integração do RTP Tools no NuttX, compilá-lo, executá-lo, identificar problemas não relacionados à aplicação, corrigi-los e, finalmente, validar o projeto antes mesmo de executá-lo em um hardware real. Executando-se a mesma aplicação em uma placa com suporte a áudio e que seja capaz de acessar a rede (via Wi-Fi e/ou cabo), podemos implementar uma espécie de placa de som que é capaz de receber pacotes de áudio através de uma rede de computadores.

Conclusão

Este artigo, que estende o trabalho iniciado em NuttX: Criando (ou Copiando!) uma Aplicação para o NuttX, mostrou como o simulador do NuttX é uma importante ferramenta de desenvolvimento de aplicações com o NuttX sem a necessidade de um hardware real. O simulador permite 1) testar a integração de aplicações externas ao NuttX, 2) depurar o funcionamento da aplicação e 3) depurar o próprio kernel do NuttX e o comportamento de suas interfaces. Desta forma, permite que comportamentos não inesperados possam ser identificados antes mesmo de utilizar a placa final, tudo no computador host!

O próximo artigo desta série irá explorar como os códigos desenvolvidos até então podem ser integrados ao repositório oficial do NuttX, permitindo assim que outros usuários tenham acesso ao código corrigido (e compatível com o padrão POSIX!) do NuttX. Falta, ainda, mostrar uma aplicação com um hardware real: chegaremos lá!

E vocês, caros leitoras e leitores, o que estão achando desta série? Fiquem atentos para conferir os próximos artigos desta série. Dúvidas, críticas e sugestões? Deixe seu comentário na página 😉

Referências

PulseAudio. Ubuntu, 2023. Disponível em: <https://wiki.ubuntu.com/PulseAudio>. Acesso em 29 de julho de 2023.

PulseAudio Volume Control 5.0, 2021. Disponível em: <https://freedesktop.org/software/pulseaudio/pavucontrol/>. Acesso em 29 de julho de 2023.

Primeiros Passos com o ESP32 e NuttX. Embarcados, 2020. Disponível em: <https://embarcados.com.br/serie/primeiros-passos-com-o-esp32-e-o-nuttx/>. Acesso em: 20 de junho de 2023.

SERRANO, Tiago Medicci. Servidor de Som: Raspberry Pi + Home Theater DIY. Embarcados, 2021. Disponível em: <https://embarcados.com.br/servidor-de-som-raspberry-pi-home-theater-diy/>. Acesso em: 20 de junho de 2023.

MONTEIRO, Sara. Primeiros Passos com o ESP32 e o NuttX. Embarcados, 2020. Disponível em: <https://embarcados.com.br/primeiros-passos-com-o-esp32-e-o-nuttx-parte-1/>. Acesso em: 1 de junho de 2023.

DE ASSIS, Alan Carvalho. O que é o RTOS NuttX e por que você deve se importar? Embarcados, 2018. Disponível em: <https://embarcados.com.br/o-que-e-o-rtos-nuttx/>. Acesso em: 1 de junho de 2023.

Getting Started. NuttX, 2023. Disponível em: <https://nuttx.apache.org/docs/latest/quickstart/index.html>. Acesso em: 2 de junho de 2023.

Repositório RTP Tools. GitHub – irtlab/rtptools, 2023. Disponível em: <https://github.com/irtlab/rtptools>. Acesso em: 2 de junho de 2023.

Repositório de Aplicações do Apache Nuttx. GitHub – apache/nuttx-apps, 2023. Disponível em: <https://github.com/apache/nuttx-apps>. Acesso em: 2 de junho de 2023.

Custom Apps How-to. Nuttx, 2023. Disponível em: <https://nuttx.apache.org/docs/latest/guides/customapps.html>. Acesso em: 02 de junho de 2023.

Repositório do Sistema Operacional do Apache Nuttx. GitHub – apache/nuttx, 2023. Disponível em: <https://github.com/apache/nuttx>. Acesso em: 2 de junho de 2023.

Repositório de Mbed-TLS. GitHub – Mbed-TLS/mbedtls, 2023. Disponível em: <https://github.com/Mbed-TLS/mbedtls>. Acesso em: 3 de junho de 2023.

Accessing the Network. NuttX, 2023. Disponível em: <https://nuttx.apache.org/docs/latest/guides/simulator.html#accessing-the-network>. Acesso em: 4 de junho de 2023.

open, openat – open file – IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008), The Open Group Base Specifications Issue 7, 2018. Disponível em: <https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html>. Acesso em 26 de agosto de 2023.

pwrite, write – write on a file – IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008), The Open Group Base Specifications Issue 7, 2018. Disponível em: <https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html>. Acesso em 26 de agosto de 2023.

Agradecimentos

Agradecimento especial aos revisores do artigo: Alan Carvalho de Assis e Sylvio Alves.

Primeiros Passos com o ESP32 e NuttX

NuttX: Criando (ou Copiando!) uma Aplicação para o NuttX
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
0 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Home » Software » NuttX: Usando o Simulador para Testes e Debugging de Aplicações

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: