In this exercise, we will establish communication between your board and an HTTP server to post information, and then remotely access that information using another HTTP client, running on a PC, tablet, or a smartphone.
Sending a POST request to the HTTP server’s /new
will return a unique 36-character client ID. We will then use this client ID as the URL when sending and retrieving the information, using PUT and GET requests to ensure it is not altered.
Pressing button 1 on the board will increment a counter and then send a PUT message with that number to our unique client ID. Pressing button 2 will send a GET request to the unique client ID to retrieve the current counter number and print it to the console. We can then use any other HTTP client and the client ID to retrieve the counter number remotely.
We will practice using the HTTP client library in nRF Connect SDK to:
In the GitHub repository for this course, go to the base code for this exercise, found in lesson5/wififund_less5_exer1
.
1. Include the HTTP client library in the application.
Enable the Kconfig for the HTTP client library.
Add the following line to the prj.conf
file
CONFIG_HTTP_CLIENT=y
Kconfig1.2 Include the header file of the HTTP Client library.
Add the following line in main.c
#include <zephyr/net/http/client.h>
C2. Define the macros for the HTTP server hostname and port.
Add the following lines to the top of main.c
#define HTTP_HOSTNAME "echo.thingy.rocks"
#define HTTP_PORT 80
C3. Declare the necessary buffers for receiving messages.
Declare recv_buf
, which will be passed to the library and used for receiving HTTP messages. Then declare client_id_buf
which will be used to store the client ID from the HTTP server. The client ID is a random string of 36 characters, so we make client_id_buf
two characters longer to add a null terminator at the end and a front slash at the beginning, making it easier to pass as a URL to the HTTP client library.
Add the following lines to main.c
#define RECV_BUF_SIZE 2048
#define CLIENT_ID_SIZE 36
static char recv_buf[RECV_BUF_SIZE];
static char client_id_buf[CLIENT_ID_SIZE+2];
C4. Define the variable for the counter as 0.
Add the following line in main.c
static int counter = 0;
C5. Define the function to retrieve the client ID.
Define the function client_get_new_id()
to send a POST request to <hostname>/new
and retrieve a unique client ID.
5.1 Define the structure http_request
and fill the block of memory
Add the following lines to the function
struct http_request req;
memset(&req, 0, sizeof(req));
C5.2 Populate the http_request
structure
Add the following code snippet
const char *headers[] = {"Connection: close\r\n", NULL};
req.header_fields = headers;
req.method = HTTP_POST;
req.url = "/new";
req.host = HTTP_HOSTNAME;
req.protocol = "HTTP/1.1";
req.response = client_id_cb;
req.recv_buf = recv_buf;
req.recv_buf_len = sizeof(recv_buf);
C5.3 Send the request to the HTTP server and close the socket.
To send the request, we will use the API call http_client_req()
.
Add the following line to the function
LOG_INF("HTTP POST request");
err = http_client_req(sock, &req, 5000, NULL);
if (err < 0) {
LOG_ERR("Failed to send HTTP POST request, err: %d", err);
}
C6. Define the callback function to handle the HTTP response for the client ID request.
Now let’s define the callback function client_id_cb
that will handle the HTTP response from the POST message sent in client_get_new_id()
.
The callback function has the following signature
6.1 Log the HTTP response status.
The HTTP response structure struct http_response
has a member http_status
, which returns the status-code from the HTTP response message. This is useful to log for debugging purposes.
Add the following line
LOG_INF("Response status: %s", rsp->http_status);
C6.2 Retrieve and format the client ID.
The client ID is found in the body of the HTTP response. The response contains the member body_frag_start
, which is the start address of the body fragment contained in recv_buf
. Use strncpy()
to copy the client ID into our temporary buffer client_id_buf_tmp
, and add a null terminator at the end.
Then we add the front slash “/” to the string, to easily be able to use it as a URL in our HTTP requests.
Add the following lines to main.c
char client_id_buf_tmp[CLIENT_ID_SIZE+1];
strncpy(client_id_buf_tmp, rsp->body_frag_start, CLIENT_ID_SIZE);
client_id_buf_tmp[CLIENT_ID_SIZE]='\0';
client_id_buf[0]='/';
strcat(client_id_buf,client_id_buf_tmp);
LOG_INF("Successfully acquired client ID: %s", client_id_buf);
C6.3 Close the socket.
After receiving a response back from the HTTP server, we want to close the socket using close()
.
LOG_INF("Closing socket: %d", sock);
close(sock);
C7. Define the function to send a PUT request to the HTTP server.
Define the function client_http_put()
to send PUT requests. This will be similar to what we did in client_get_new_id()
, apart from a few changes. For the method
, we specify HTTP_PUT
. For the url
, we provide the client ID, and now we want the response callback to be response_cb
.
We also need to set payload
and payload_len
to what we want to send with the request, i.e the counter variable, counter
. Since payload
expects const char *
, we use snprintf()
to copy the counter into a buffer.
Add the following code snippet
int err = 0;
int bytes_written;
const char *headers[] = {"Connection: close\r\n", NULL};
struct http_request req;
memset(&req, 0, sizeof(req));
char buffer[12] = {0};
bytes_written = snprintf(buffer, 12, "%d", counter);
if (bytes_written < 0){
LOG_ERR("Unable to write to buffer, err: %d", bytes_written);
return bytes_written;
}
req.header_fields = headers;
req.method = HTTP_PUT;
req.url = client_id_buf;
req.host = HTTP_HOSTNAME;
req.protocol = "HTTP/1.1";
req.payload = buffer;
req.payload_len = bytes_written;
req.response = response_cb;
req.recv_buf = recv_buf;
req.recv_buf_len = sizeof(recv_buf);
LOG_INF("HTTP PUT request: %s", buffer);
err = http_client_req(sock, &req, 5000, NULL);
if (err < 0) {
LOG_ERR("Failed to send HTTP PUT request %s, err: %d", buffer, err);
}
return err;
C8. Define the function to send a GET request to the HTTP server.
Define the function client_http_get()
to send a GET request to the same URL to retrieve the message.
Add the following code snippet
int err = 0;
const char *headers[] = {"Connection: close\r\n", NULL};
struct http_request req;
memset(&req, 0, sizeof(req));
req.header_fields = headers;
req.method = HTTP_GET;
req.url = client_id_buf;
req.host = HTTP_HOSTNAME;
req.protocol = "HTTP/1.1";
req.response = response_cb;
req.recv_buf = recv_buf;
req.recv_buf_len = sizeof(recv_buf);
LOG_INF("HTTP GET request");
err = http_client_req(sock, &req, 5000, NULL);
if (err < 0) {
LOG_ERR("Failed to send HTTP GET request, err: %d", err);
}
return err;
C9. Define the callback function response_cb
to print the body.
Define the callback function to handle the HTTP response from the PUT and GET messages.
The callback function once again prints the status of the response, found in http_status
. Then if the response has a body, it will print it as well. Once we have received the response, we want to close the socket.
Add the following code snippet to your code
LOG_INF("Response status: %s", rsp->http_status);
if (rsp->body_frag_len > 0) {
char body_buf[rsp->body_frag_len];
strncpy(body_buf, rsp->body_frag_start, rsp->body_frag_len);
body_buf[rsp->body_frag_len]='\0';
LOG_INF("Received: %s", body_buf);
}
LOG_INF("Closing socket: %d", sock);
close(sock);
C10. Define the button handler to send requests upon button triggers.
Upon pressing button 1, we want the button handler to send the counter value to the HTTP server via a PUT request and then increment the counter. On the other hand, we want it to send a GET request to read that value upon pressing button 2.
Add the following code snippet to main.c
if (has_changed & DK_BTN1_MSK && button_state & DK_BTN1_MSK) {
if (server_connect() >= 0) {
client_http_put();
counter++;
}
} else if (has_changed & DK_BTN2_MSK && button_state & DK_BTN2_MSK) {
if (server_connect() >= 0) {
client_http_get();
}
}
C11. Retrieve the client ID upon connection with the server.
Call client_get_new_id()
from main()
, and print the client ID to the console.
Add the following lines to main()
if (client_get_new_id() < 0) {
LOG_INF("Failed to get client ID");
return 0;
}
C12. Build and flash the application to your board.
This exercise uses the PSA backend for storing the Wi-Fi credentials. Therefore, you must build with TF-M.
Board | Build with TF-M | Extra CMake arguments |
---|---|---|
nRF7002 DK | nrf7002dk_nrf5340_cpuapp_ns | N/A |
nRF5340 DK + nRF7002 EK | nrf5340dk_nrf5340_cpuapp_ns | -DSHIELD=nrf7002ek |
If necessary, input the commands to connect to Wi-Fi, as we have done in previous exercises.
When a Wi-Fi connection is established, observe the following log output from the application.
*** Booting nRF Connect SDK 2.6.1-3758bcbfa5cd ***
[00:00:01.493,65OK
2] <inf> Lesson5_Exercise1: Waiting to connect to Wi-Fi
[00:00:07.130,737] <inf> Lesson5_Exercise1: Network connected
[00:00:07.254,425] <inf> Lesson5_Exercise1: IPv4 address of HTTP server found 143.xxx.xxx.xxx
[00:00:07.282,348] <inf> Lesson5_Exercise1: Connected to server
[00:00:07.282,456] <inf> Lesson5_Exercise1: HTTP POST request
[00:00:08.487,518] <inf> Lesson5_Exercise1: Response status: Created
[00:00:08.487,609] <inf> Lesson5_Exercise1: Successfully acquired client ID: /<your_client_ID>
[00:00:08.487,609] <inf> Lesson5_Exercise1: Closing socket: 9
TerminalTesting
13. Set up an HTTP client.
To test the application, we need to setup an HTTP client to remotely read the counter value that was sent to the HTTP server. You will need an HTTP client running on your PC, smartphone, or tablet. In this exercise, we will use HTTPie’s web app, an in-browser HTTP client that does not require any installation or setup.
14. Copy the client ID from your device’s log output.
Copy the unique client ID that your device retrieves when first connecting to the HTTP server, it is marked in the log above as <your_client_ID>
. You will need your device’s specific client ID to be able to retrieve the value that your device has posted to the server.
15. Open a web browser and go to httpie.io/app.
GET
.echo.thingy.rocks
followed by your specific client ID. 16. Observe the response from the server, number “4” for example, at the bottom of this window.