quinta-feira, novembro 10, 2022

Usando o WiFi da Raspberry Pi PIco W (Parte 5)

 Vamos agora acessar uma API REST com a Raspberry Pi Pico, usando o SDK C/C++.

Exemplo de chamada a uma API REST usando o Postman


Simplificando bastante, uma API REST é (tipicamente) o uso dos métodos HTTP (como GET e POST) para o acesso e manipulação de "recursos". Como exemplo vamos usar uma API de consulta a CEPs. O método que vamos chamar permite obter informações sobre um CEP:

GET http:viacep.com.br/ws/{cep}/json/

onde  {cep} é o cep a consultar (oito digitos sem separadores).

A biblioteca lwIP possui um módulo HTTP client para realizar acessos HTTP. Este módulo possui dois métodos: httpc_get_file() e httpc_get_file_dns(). Pode trazer um pouco de confusão o fato dos métodos se chamarem "get file", originalmente o objetivo do HTTP era acessar arquivos de um servidor web.

Pode também parecer que o segundo método apenas resolve o nome via DNS e depois trata tudo igual ao primeiro, mas existe uma pequena (e importante) diferença: o segundo método coloca nos cabeçalhos HTTP o nome do servidor. É comum um servidor (com um IP) atender ao pedido referente a vários nomes e usar o nome no cabeçalho para escolher o que deve fazer.

De resto temos a velha brincadeira de callbacks. A assinatura de httpc_get_file_dns() é:

err_t 	 httpc_get_file_dns (
   const char *server_name, 
   u16_t port, 
   const char *uri, 
   const httpc_connection_t *settings,
   altcp_recv_fn recv_fn, 
   void *callback_arg, 
   httpc_state_t **connection)

Onde httpc_connection_t contém

typedef struct _httpc_connection {
  ip_addr_t proxy_addr;
  u16_t proxy_port;
  u8_t use_proxy;
  /* this callback is called when the transfer is finished (or aborted) */
  httpc_result_fn result_fn;
  /* this callback is called after receiving the http headers
     It can abort the connection by returning != ERR_OK */
  httpc_headers_done_fn headers_done_fn;
} httpc_connection_t;

Temos, portanto, três callbacks envolvidos:

  • recv_fn é onde recebemos os dados
  • result_fn é onde se recebe somente o resultado final (sem os dados)
  • headers_done_fn é onde se recebe os cabeçalhos da resposta

Visto tudo isso podemos fazer um exemplo simples, consultando o CEP 01001000 e enviando o resultado para stdio:

  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <time.h>  
  4.   
  5. #include "pico/stdlib.h"  
  6. #include "pico/cyw43_arch.h"  
  7. #include "hardware/i2c.h"  
  8.   
  9. #include "lwip/pbuf.h"  
  10. #include "lwip/tcp.h"  
  11. #include "lwip/apps/http_client.h"  
  12. #include "secret.h"  
  13.   
  14. char myBuff[2000];  
  15.   
  16. // chamada quando termina a transferência  
  17. void recebeu_resultado(void *arg, httpc_result_t httpc_result,  
  18.         u32_t rx_content_len, u32_t srv_res, err_t err)  
  19.   
  20. {  
  21.     printf("\nTransferencia finalizada\n");  
  22.     printf("resultado=%d\n", httpc_result);  
  23.     printf("código http=%d\n", srv_res);  
  24. }  
  25.   
  26. // chamada quando recebeu os cabeçalhos  
  27. err_t recebeu_headers(httpc_state_t *connection, void *arg,   
  28.     struct pbuf *hdr, u16_t hdr_len, u32_t content_len)  
  29. {  
  30.     printf("\nRecebeu cabecalhos\n");  
  31.     printf("Tamanho dos dados=%d\n", content_len);  
  32.     printf("Tamanho dos cabeçalhos %d\n", hdr_len);  
  33.     pbuf_copy_partial(hdr, myBuff, hdr_len, 0);  
  34.     myBuff[hdr_len] = 0;  
  35.     printf("Cabeçalhos: \n");  
  36.     printf("%s", myBuff);  
  37.     printf("\nCorpo da resposta:\n");  
  38.     return ERR_OK;  
  39. }  
  40.   
  41. // chamada quando recebeu dados  
  42. err_t recebeu_dados(void *arg, struct altcp_pcb *conn,   
  43.                             struct pbuf *p, err_t err)  
  44. {  
  45.     pbuf_copy_partial(p, myBuff, p->tot_len, 0);  
  46.     myBuff[p->tot_len] = 0;  
  47.     printf("%s", myBuff);  
  48.     return ERR_OK;  
  49. }  
  50.   
  51.   
  52. // Programa Principal  
  53. int main() {  
  54.     // Inicia stdio e aguarda conectar USB  
  55.     stdio_init_all();  
  56.     while (!stdio_usb_connected()) {  
  57.         sleep_ms(100);  
  58.     }  
  59.     printf("\nTeste API REST\n");  
  60.   
  61.     // Inicia CYW43 e defeine o pais  
  62.     if (cyw43_arch_init_with_country(CYW43_COUNTRY_BRAZIL)) {  
  63.         printf("failed to initialise\n");  
  64.         return 1;  
  65.     }  
  66.   
  67.     // Inicia WiFi no modo Station  
  68.     cyw43_arch_enable_sta_mode();  
  69.   
  70.     // Tenta conectar  
  71.     if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD,   
  72.         CYW43_AUTH_WPA2_AES_PSK, 10000)) {  
  73.         printf("Erro ao conectar WiFi\n");  
  74.         return 1;  
  75.     }  
  76.     char sIP[] = "xxx.xxx.xxx.xxx";  
  77.     strcpy (sIP, ip4addr_ntoa(netif_ip4_addr(netif_list)));  
  78.     printf ("Conectado, IP %s\n", sIP);  
  79.   
  80.     // Acessa a API  
  81.     httpc_connection_t settings;  
  82.     settings.result_fn = recebeu_resultado;  
  83.     settings.headers_done_fn = recebeu_headers;  
  84.   
  85.     cyw43_arch_lwip_begin();  
  86.     err_t err = httpc_get_file_dns(  
  87.             "viacep.com.br", 80, "/ws/01001000/json/",  
  88.             &settings, recebeu_dados, NULL, NULL  
  89.         );   
  90.     cyw43_arch_lwip_end();  
  91.   
  92.     printf("Status imediato: %d \n", err);  
  93.     while (true){  
  94.         #if PICO_CYW43_ARCH_POLL  
  95.         cyw43_arch_poll();  
  96.         sleep_ms(1);  
  97.         #else  
  98.         sleep_ms(10);  
  99.         #endif  
  100.     }  
  101.       
  102.     cyw43_arch_deinit();  
  103.     return 0;      
  104. }  

O resultado obtido é:

Teste API REST
Conectado, IP 192.168.15.60
Status imediato: 0

Recebeu cabecalhos
Tamanho dos dados=-1
Tamanho dos cabeçalhos 494
Cabeçalhos:
HTTP/1.1 200 OK
Server: nginx/1.22.0
Date: Tue, 08 Nov 2022 23:16:17 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: close
Expires: Wed, 09 Nov 2022 00:16:17 GMT
Cache-Control: max-age=3600
Pragma: public
Cache-Control: public
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Access-Control-Allow-Headers: Content-Type, X-Request-With, X-Requested-By
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400


Corpo da resposta:
e0
{
  "cep": "01001-000",
  "logradouro": "Praça da Sé",
  "complemento": "lado ímpar",
  "bairro": "Sé",
  "localidade": "São Paulo",
  "uf": "SP",
  "ibge": "3550308",
  "gia": "1004",
  "ddd": "11",
  "siafi": "7107"
}
0


Transferencia finalizada
resultado=0
código http=200

O código acima é bastante grosseiro e limitado. A maioria das APIs utilizam https, o que requer o estabelecimento de uma conexão criptografada (o que ainda preciso estudar como fazer). O módulo HTTP Client não suporta redirecionamento (o que quebrou o primeiro exemplo que eu tentei).

Quem estiver curioso com como é acessar uma API REST usando MicroPython, fique de olho no blog Maker Hero onde vai ser publicado um artigo meu sobre os primeiros passos com a Pi Pico. De um modo geral é bem mais simples (inclusive usar https, porém tive problemas com a acentuação na resposta da API que estou usando aqui);

No próximo (e talvez último) post desta série vamos ver como fazer um webserver rudimentar, permitindo interagir com a Pico W através de um browser.


Um comentário:

Anônimo disse...

Daniel, gostei dos seus projetos em raspberry. Estou fazendo um curso no IFCE, aqui em Fortaleza-Ce., de sistemas embarcados, utilizando a Raspberry Pi Pico W, em linguagem de programação C (visual code). Estou pensando em fazer um projeto para entregar no final do curso que: vou utilizar um mEedidor de temperatura DH22, medir a temperatura, a aumidade, me conectar na internet via Wifi da placa, conectar no e-mail e enviar menssagem para os responsáveis, quando a temperarura e a umidade estiverem fora dos padrões de normalidade. Pergunto você tem alguma coisa que poderia me ajudar a implementar tal projeto?
Menu nome é: Raimu do Eumazio Rocha
E-mail: rocharaimun@gmail.com