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:

#include <stdio.h>
#include <string.h>
#include <time.h>

#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "hardware/i2c.h"

#include "lwip/pbuf.h"
#include "lwip/tcp.h"
#include "lwip/apps/http_client.h"
#include "secret.h"

char myBuff[2000];

// chamada quando termina a transferência
void recebeu_resultado(void *arg, httpc_result_t httpc_result,
        u32_t rx_content_len, u32_t srv_res, err_t err)

{
    printf("\nTransferencia finalizada\n");
    printf("resultado=%d\n", httpc_result);
    printf("código http=%d\n", srv_res);
}

// chamada quando recebeu os cabeçalhos
err_t recebeu_headers(httpc_state_t *connection, void *arg, 
    struct pbuf *hdr, u16_t hdr_len, u32_t content_len)
{
    printf("\nRecebeu cabecalhos\n");
    printf("Tamanho dos dados=%d\n", content_len);
    printf("Tamanho dos cabeçalhos %d\n", hdr_len);
    pbuf_copy_partial(hdr, myBuff, hdr_len, 0);
    myBuff[hdr_len] = 0;
    printf("Cabeçalhos: \n");
    printf("%s", myBuff);
    printf("\nCorpo da resposta:\n");
    return ERR_OK;
}

// chamada quando recebeu dados
err_t recebeu_dados(void *arg, struct altcp_pcb *conn, 
                            struct pbuf *p, err_t err)
{
    pbuf_copy_partial(p, myBuff, p->tot_len, 0);
    myBuff[p->tot_len] = 0;
    printf("%s", myBuff);
    return ERR_OK;
}


// Programa Principal
int main() {
    // Inicia stdio e aguarda conectar USB
    stdio_init_all();
    while (!stdio_usb_connected()) {
        sleep_ms(100);
    }
    printf("\nTeste API REST\n");

    // Inicia CYW43 e defeine o pais
    if (cyw43_arch_init_with_country(CYW43_COUNTRY_BRAZIL)) {
        printf("failed to initialise\n");
        return 1;
    }

    // Inicia WiFi no modo Station
    cyw43_arch_enable_sta_mode();

    // Tenta conectar
    if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, 
        CYW43_AUTH_WPA2_AES_PSK, 10000)) {
        printf("Erro ao conectar WiFi\n");
        return 1;
    }
    char sIP[] = "xxx.xxx.xxx.xxx";
    strcpy (sIP, ip4addr_ntoa(netif_ip4_addr(netif_list)));
    printf ("Conectado, IP %s\n", sIP);

    // Acessa a API
    httpc_connection_t settings;
    settings.result_fn = recebeu_resultado;
    settings.headers_done_fn = recebeu_headers;

    cyw43_arch_lwip_begin();
    err_t err = httpc_get_file_dns(
            "viacep.com.br", 80, "/ws/01001000/json/",
            &settings, recebeu_dados, NULL, NULL
        ); 
    cyw43_arch_lwip_end();

    printf("Status imediato: %d \n", err);
    while (true){
        #if PICO_CYW43_ARCH_POLL
        cyw43_arch_poll();
        sleep_ms(1);
        #else
        sleep_ms(10);
        #endif
    }
    
    cyw43_arch_deinit();
    return 0;    
}

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.


Nenhum comentário: