Can I make a Swift-wrapped HTTP GET request from the ESP32C6?
This article is part of a series.
- Part 1: Can I program a an ESP32C6 with the esp-idf?
- Part 2: Can I program an ESP32C6 with the Swift example code?
- Part 3: How does the espressif SDK handle inputs?
- Part 4: How does the espressif SDK handle wifi?
- Part 5: Can I combine an LED and a button on the ESP32C6 with Swift?
- Part 6: Can I add Wifi to the ESP32C6 project with Swift?
- Part 7: This Article
So for this I created another component to act as a bridge. Right now this turned out to be mostly C while I figure out how to / whether to bring in things like URL and URL session, HTTPRequest…
I’ve written a client in Swift before, but it relied heavily on Foundation types. The only example in the the Swift Embedded Examples repo that uses Foundation is a beautiful Swift MMIO based example for the stm32 which is making me rethink my chip choices. Although SVD data is available for the esp32c6 even though it’s RISCV, not ARM.
As an alternative to Foundation, Hummingbird brings in SwiftNIO.
Both Foundation and SwiftNIO are very large and I’m not in the mood to wrestle with the CMake file today.
While I overthink this, I just made the proof of concept version with C.
The Swift Files
Main.swift
Right above the while loop but after the delay to wait for the WiFi to connect…
//Waiting for wifi to connect...
delay(2000);
let client = HTTPClient()
client.getAndPrint(from: "example.com", route: "/")
HTTP.swift
Again with the ugly string passing which I hope is temporary.
THe result code is something I could turn into a thrown error at some point. It’s a really common pattern in C that the result is written at a shared pointer and the error code is the thing that’s returned.
final class HTTPClient {
func getAndPrint(from host: String, route: String) {
print("Getting...")
print(http_bridge_bridge_return_twelve())
let local_host = host.utf8CString
let local_route = route.utf8CString
//TODO: test with span on beta?
local_route.withContiguousStorageIfAvailable { route_buffer in
local_host.withContiguousStorageIfAvailable { host_buffer in
let resultCode = http_bridge_get(host_buffer.baseAddress, route_buffer.baseAddress)
print("result code: \(resultCode)")
}
}
}
}
The Component Code
The esp-idf has a HTTP Client library, which a more robust version of this code could use. esp-idf example
The C File
Similar code is in the HTTP_Request example.
Both do the following basic steps:
- find out if the host name even exists as a valid URL
- make a socket
- use the socket to connect
- send the request over the socket
- say done sending
- receive the request
- … the WHOLE request
- close the socket
Making sure to close the socket in case of any failures as well.
int http_bridge_get(const char *host, const char *path) {
printf("A");
struct addrinfo hints = {
.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM,
};
struct addrinfo *res;
int s;
printf("B");
char port_str[] = "80";
int err = getaddrinfo(host, port_str, &hints, &res);
//int err = getaddrinfo(host, port_str, &hints, &res);
if (err != 0 || res == NULL) {
printf("DNS lookup failed for %s", host);
return 1;
}
printf("C");
s = socket(res->ai_family, res->ai_socktype, 0);
if (s < 0) {
printf("socket failed");
freeaddrinfo(res);
return 2;
}
printf("D");
if (connect(s, res->ai_addr, res->ai_addrlen) != 0) {
printf("connect failed");
close(s);
freeaddrinfo(res);
return 3;
}
printf("E");
freeaddrinfo(res);
printf("F");
char req[128];
snprintf(req, sizeof(req),
"GET %s HTTP/1.0\r\n"
"Host: %s\r\n"
"User-Agent: esp-idf/5.5\r\n"
"\r\n",
path, host);
if (write(s, req, strlen(req)) < 0) {
printf("send failed");
close(s);
return 4;
}
char recv_buf[512];
int r;
while ((r = read(s, recv_buf, sizeof(recv_buf) - 1)) > 0) {
recv_buf[r] = 0; // null terminate
printf("%s", recv_buf);
}
close(s);
return 0;
}
The header
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
int http_bridge_bridge_return_twelve(void);
int http_bridge_get(const char *host, const char *path);
The CMake
No REQUIRED in this because the includes are part of the the C implementation.
idf_component_register(SRCS "http_bridge.c"
INCLUDE_DIRS "include")
Summary
Kinda punted on this part a bit while I figure out how much time I have to really dig in. Need to do some parts ordering, get the real server up, etc.
This article is part of a series.
- Part 1: Can I program a an ESP32C6 with the esp-idf?
- Part 2: Can I program an ESP32C6 with the Swift example code?
- Part 3: How does the espressif SDK handle inputs?
- Part 4: How does the espressif SDK handle wifi?
- Part 5: Can I combine an LED and a button on the ESP32C6 with Swift?
- Part 6: Can I add Wifi to the ESP32C6 project with Swift?
- Part 7: This Article