Using WiFi in the Raspberry Pi Pico (Part 5)

In this post we will see how to access a REST API with the Raspberry Pi Pico W, using the SDK C/C++ and MicroPython.

An example of a call to a RESP API, using Postman

Basically, a REST API is uses an HTTP method (like GET and POST) to access and manipulate "resources".

Using the C/C++ SDK

For my first example I will use an API that gives information on Brazilian postcodes ("CEP"):

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

where  {cep} is the 8 digit postcode we want to consult.

The lwIP library have an HTTP_ client module for HTTP access. This module have two methods:  httpc_get_file() and httpc_get_file_dns().  The "get_file" part may be confusing, originally HTTP was only used to access files in the web server.

It may also seem that the second method only resolves the server name through DNS and then does the same thing as the first method, but there is an import difference: the second method puts the server name in the HTTP headers. It is very common for a web server (with a single IP) answer requests that come to different server names and use the name in the header to figure out what it needs to do.

Now all we have to do is the old callback programming. The signature of  httpc_get_file_dns() is:

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)

Where httpc_connection_t contains:

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;

So there are three callbacks involved:

  • recv_fn is where we get the data
  • result_fn is where we get the final result (but not the data)
  • headers_done_fn is where we get the headers in the response

Let's write a very simple example that consults the 01001000 CEP and shows the response in the standard output:
#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];

// called at the end of the transfer
void got_result(void *arg, httpc_result_t httpc_result,
        u32_t rx_content_len, u32_t srv_res, err_t err)

{
    printf("\nTransfer fineshed\n");
    printf("result=%d\n", httpc_result);
    printf("http code=%d\n", srv_res);
}

// called when we get the headers
err_t got_headers(httpc_state_t *connection, void *arg, 
    struct pbuf *hdr, u16_t hdr_len, u32_t content_len)
{
    printf("\nHeaders received\n");
    printf("Content length=%d\n", content_len);
    printf("Header length %d\n", hdr_len);
    pbuf_copy_partial(hdr, myBuff, hdr_len, 0);
    myBuff[hdr_len] = 0;
    printf("Headers: \n");
    printf("%s", myBuff);
    printf("\nContent body:\n");
    return ERR_OK;
}

// called when we get the data
err_t got_data(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;
}


// Main program
int main() {
    // Start stdio and wait USB connected
    stdio_init_all();
    while (!stdio_usb_connected()) {
        sleep_ms(100);
    }
    printf("\nREST API Test\n");

    // Start CYW43 and select country
    if (cyw43_arch_init_with_country(CYW43_COUNTRY_BRAZIL)) {
        printf("failed to initialise\n");
        return 1;
    }

    // Start WiFi in Station Mode
    cyw43_arch_enable_sta_mode();

    // Try to connect
    if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, 
        CYW43_AUTH_WPA2_AES_PSK, 10000)) {
        printf("Error connecting WiFi\n");
        return 1;
    }
    char sIP[] = "xxx.xxx.xxx.xxx";
    strcpy (sIP, ip4addr_ntoa(netif_ip4_addr(netif_list)));
    printf ("Connected, IP %s\n", sIP);

    // Access the API
    httpc_connection_t settings;
    settings.result_fn = got_result;
    settings.headers_done_fn = got_headers;

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

    printf("Immediate Status: %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;    
}
This code is very crude and limited. Most API nowadays use https, which requires an encrypted connection. Also, the HTTP module does not support redirects (this broke my first try).

Using MicroPython

MicroPython makes things easier and does support https. For this example I will use a free and open API for information on the Star Wars movies. For the full documentation, access https://swapi.dev/documentation. A query is done by acessing https://swapi.dev/api/films/{film}/ where{film} is the film number (1 to 9 in the release order). The answer comes in json format:

{
    "characters": [
        "https://swapi.dev/api/people/1/",
        ...
    ],
    "created": "2014-12-10T14:23:31.880000Z",
    "director": "George Lucas",
    "edited": "2014-12-12T11:24:39.858000Z",
    "episode_id": 4,
    "opening_crawl": "It is a period of civil war...",
    "planets": [
        "https://swapi.dev/api/planets/1/",
        ...
    ],
    "producer": "Gary Kurtz, Rick McCallum",
    "release_date": "1977-05-25",
    "species": [
        "https://swapi.dev/api/species/1/",
        ...
    ],
    "starships": [
        "https://swapi.dev/api/starships/2/",
        ...
    ],
    "title": "A New Hope",
    "url": "https://swapi.dev/api/films/1/",
    "vehicles": [
        "https://swapi.dev/api/vehicles/4/",
        ...
    ]
}

The code bellow consult the data on the first film and prints its title and opening crawl text.
import rp2
import network
import time
import urequests
import json
 
film = 1
url = 'https://swapi.dev/api/films/{0}/'.format(film)
TIMEOUT = 10
 
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect('essid', 'password')
 
print ('Connecting...')
 
timeout = time.ticks_add(time.ticks_ms(), TIMEOUT*1000)
while not wlan.isconnected() and wlan.status() >= 0 and \
      time.ticks_diff(timeout, time.ticks_ms()) > 0:
    time.sleep(0.2)
     
if wlan.isconnected():
    print ('Connected')
    print ('Querying film {0}'.format(film))
    r = urequests.get(url)
    if r.status_code == 200:
        response = json.loads(r.text)
        print ('Episode {0}: {1}'.format(response['episode_id'],
               response['title']))
        print (response['opening_crawl'])
    else:
        print ('ERROR: {0}'.format(r.status_code));
    r.close()
else:
    print ('ERROR: Could not connect the WiFi!')
 
wlan.active(False)

Comments

Popular posts from this blog

Using the PIO to Interface a PS/2 Keyboard