Using WiFiEspAT on the Challenger NB RP2040 WiFi to Access a REST API
The Challenger NB RP2040 WiFi board combines the deterministic performance of the RP2040 with an integrated ESP8285 WiFi coprocessor running ESP-AT firmware. This architecture makes it possible to add full TCP/IP connectivity to an RP2040 application without running a WiFi stack locally on the MCU.
In this article, we demonstrate how to use WiFiEspAT to access a public REST API and retrieve real-world data — in this case, sunrise and sunset times from the Sunrise-Sunset API.
System Architecture
On the Challenger NB RP2040 WiFi:
- The RP2040 runs your Arduino application.
- The ESP8285 handles WiFi and TCP/IP.
- Communication between the two devices happens over UART using AT commands.
- The Arduino-side interface is provided by the
WiFiEspATlibrary.
This separation provides several advantages:
- Deterministic timing on the RP2040
- Reduced firmware complexity
- Clear separation between application logic and network stack
- Stable and well-tested ESP TCP/IP implementation
Target: Sunrise-Sunset REST API
We use the public Sunrise-Sunset REST endpoint:
http://api.sunrise-sunset.org/json?lat=55.6050&lng=13.0038&formatted=0&tzid=Europe/Stockholm
The API returns JSON:
{
"results": {
"sunrise": "2026-02-22T07:14:03+01:00",
"sunset": "2026-02-22T17:21:44+01:00"
},
"status": "OK"
}
The tzid parameter allows us to receive timestamps already converted to local time, eliminating the need for manual UTC conversion or DST handling.
Required Libraries
- WiFiEspAT – ESP-AT WiFi interface
- ArduinoJson – JSON parsing
- ArduinoHttpClient – robust HTTP handling
All libraries are available through the Arduino Library Manager.
Initialization on Challenger NB RP2040 WiFi
The board support package includes helper functions for controlling the ESP8285. A typical initialization sequence looks like this:
#include <ChallengerWiFi.h>
#include <WiFiEspAT.h>Challenger2040WiFi.reset(); // Hardware reset of ESP8285
WiFi.init(Serial2); // UART link to ESP8285
WiFi.begin(ssid, password);
Serial2 is internally routed to the ESP8285 on this board, so no manual pin configuration is required.
Performing the HTTP Request
Using ArduinoHttpClient, the REST call becomes straightforward:
WiFiClient client;
HttpClient http(client, "api.sunrise-sunset.org", 80);http.get("/json?lat=55.6050&lng=13.0038&formatted=0&tzid=Europe/Stockholm");int status = http.responseStatusCode();
String body = http.responseBody();
The HttpClient class handles HTTP headers and chunked transfer encoding automatically, which avoids common parsing issues.
Parsing the JSON Response
With ArduinoJson v7:
JsonDocument doc;
deserializeJson(doc, body);const char* sunrise = doc["results"]["sunrise"];
const char* sunset = doc["results"]["sunset"];
This provides direct access to structured API data without manual string processing.
Why This Architecture Works Well
Using WiFiEspAT on the Challenger NB RP2040 WiFi offers a clean and robust way to integrate cloud connectivity into embedded systems:
- No WiFi stack running on the RP2040
- Lower RAM usage on the MCU
- Mature and stable ESP networking firmware
- Clear debugging over UART
- Easy integration into existing Arduino-based projects
The RP2040 remains focused on deterministic control tasks, while the ESP coprocessor handles the full TCP/IP complexity.
Practical Applications
The same approach can be used to access:
- Weather APIs
- MQTT brokers
- REST-based IoT platforms
- Time synchronization services
- Custom backend systems
Any TCP/IP-based protocol supported by ESP-AT can be accessed from the RP2040 using this method.
Conclusion
The Challenger NB RP2040 WiFi provides a powerful yet simple method for bringing cloud connectivity to RP2040-based systems. By leveraging WiFiEspAT and standard Arduino networking libraries, it is possible to turn the RP2040 into a capable REST client with minimal overhead.
The separation between MCU and WiFi coprocessor results in a clean architecture that scales well from simple prototypes to production systems.
Complete source code
#include <Arduino.h>
#include <ArduinoHttpClient.h>
#include <ChallengerWiFi.h> // iLabs helper (reset + baudrate etc.)
#include <WiFiEspAT.h> // AT based WiFi-stack
#include <ArduinoJson.h> // JSON-parsing
// ====== WiFi credentials ======
static const char* WIFI_SSID = "FarmNet";
static const char* WIFI_PASS = "CountlessHours1024";
// ====== Sunrise-Sunset API ======
static const char* HOST = "api.sunrise-sunset.org";
static const uint16_t PORT = 80; // HTTP (Simple and robust for ESP-AT)
static const char* DATE = "today"; // Or you can get for a specific date "YYYY-MM-DD"
static const char* TZID = "Europe/Stockholm"; // Local time
// Ex: Malmö
static const double LAT = 55.6050;
static const double LNG = 13.0038;
static void wifiConnect()
{
Serial.println("Resetting ESP8285...");
if (Challenger2040WiFi.reset()) {
Serial.println("WiFi chip reset OK!");
} else {
Serial.println("Could not reset WiFi chip!");
while (true) delay(1000);
}
WiFi.init(Serial2);
if (WiFi.status() == WL_NO_MODULE) {
Serial.println("Communication with WiFi module failed (WL_NO_MODULE)!");
while (true) delay(1000);
}
Serial.print("Connecting to WiFi: ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("WiFi connected. IP: ");
Serial.println(WiFi.localIP());
}
static bool fetchSunTimes(String& sunrise, String& sunset)
{
WiFiClient client;
HttpClient http(client, HOST, PORT);
char path[256];
snprintf(path, sizeof(path),
"/json?lat=%.6f&lng=%.6f&date=%s&formatted=0&tzid=%s",
LAT, LNG, DATE, TZID);
int err = http.get(path);
if (err != 0) {
Serial.print("http.get() failed, err=");
Serial.println(err);
return false;
}
int status = http.responseStatusCode();
String body = http.responseBody(); // <-- handle headers/chunking
Serial.print("HTTP status: ");
Serial.println(status);
// Debug error: output the beginning of the response
if (status < 200 || status >= 300) {
Serial.println("Non-2xx response body (first 300 chars):");
Serial.println(body.substring(0, 300));
return false;
}
if (body.length() == 0) {
Serial.println("Empty HTTP body.");
return false;
}
JsonDocument doc;
DeserializationError jerr = deserializeJson(doc, body);
if (jerr) {
Serial.print("JSON parse failed: ");
Serial.println(jerr.c_str());
Serial.println("Body (first 300 chars):");
Serial.println(body.substring(0, 300));
return false;
}
const char* apiStatus = doc["status"] | "";
if (strcmp(apiStatus, "OK") != 0) {
Serial.print("API status not OK: ");
Serial.println(apiStatus);
Serial.println("Body (first 300 chars):");
Serial.println(body.substring(0, 300));
return false;
}
sunrise = (const char*)doc["results"]["sunrise"];
sunset = (const char*)doc["results"]["sunset"];
return true;
}
void setup()
{
Serial.begin(115200);
delay(200);
wifiConnect();
String sunrise, sunset;
if (fetchSunTimes(sunrise, sunset)) {
Serial.print("Sunrise (");
Serial.print(TZID);
Serial.print("): ");
Serial.println(sunrise);
Serial.print("Sunset (");
Serial.print(TZID);
Serial.print("): ");
Serial.println(sunset);
} else {
Serial.println("Failed to fetch sunrise/sunset.");
}
}
void loop()
{
// The example is executed once during setup.
delay(5000);
}