devicescript-http-fetch-api

HTTP Requests with fetch in DeviceScript

  • 5 min

DeviceScript’s fetch function allows you to perform HTTP or HTTPS requests using an async API similar to the browser’s.

In the Arduino/C++ ecosystem, making a secure web request (HTTPS) is often a herculean task. You have to manage SSL certificates, ensure the response fits in RAM, and then use a library like ArduinoJson to try to decipher that alphabet soup you receive.

We can download text, process JSON, and send data without incorporating an external HTTP stack. We are still working on a microcontroller, so response sizes and TLS overhead matter.

The fetch function

The Fetch API is the modern standard for performing asynchronous network requests. In DeviceScript, it is imported from @devicescript/net.

The basic structure is:

We call fetch(url).

We await the server’s response.

We convert the response to text or JSON.

We await the processing of that content.

GET Request: reading JSON

Let’s start with the basics: querying a public API to get data. We’ll use the classic testing service JSONPlaceholder.

We’ll fetch information from a sample todo item.

import { fetch } from "@devicescript/net"

// Test URL that returns a simple JSON
const URL = "https://jsonplaceholder.typicode.com/todos/1"

async function getData() {
    console.log("Starting GET request...")

    try {
        // 1. Make the request
        const response = await fetch(URL)

        // 2. Check if the server gave us the OK (Status code 200-299)
        if (response.status === 200) {
            
            // 3. Parse the JSON
            const data = await response.json()

            // 4. Use the data as a normal JS object
            console.log("Data received:")
            console.log(`ID: ${data.id}`)
            console.log(`Title: ${data.title}`)
            console.log(`Completed: ${data.completed}`)

        } else {
            console.error(`Server error: ${response.status}`)
        }

    } catch (error) {
        // Catch network errors (e.g., WiFi down)
        console.error("Network error:", error)
    }
}

// Execute
getData()
Copied!

Analyzing the differences from C++

  • No WiFiClientSecure: DeviceScript manages the secure TLS connection under the hood.
  • No DeserializationError: .json() returns a ready-to-use JavaScript object.
  • Memory: The system manages buffers, but the response must fit in the available memory. Keep documents small.

The simulator can use the computer’s network, allowing you to test the API response before trying it on the board.

POST Request: sending data

Reading is easy, but for IoT we usually want to send data (sensor telemetry) to a server. For that, we use the POST method.

Suppose we want to send the current temperature to a server. We need to configure the fetch call a bit more:

import * as ds from "@devicescript/core"
import { fetch } from "@devicescript/net"

async function sendTelemetry(temperature: number) {
    const POST_URL = "https://jsonplaceholder.typicode.com/posts"
    
    console.log(`Sending temperature: ${temperature}...`)

    try {
        const response = await fetch(POST_URL, {
            method: "POST", // Indicating we are writing
            headers: {
                // Important: Tell the server we are sending JSON
                "Content-Type": "application/json" 
            },
            // Convert our JS object to text to send it
            body: JSON.stringify({
                sensor: "ESP32-Lab",
                temp: temperature,
                uptime: ds.millis()
            })
        })

        if (response.status === 201) { // 201 = Created
            console.log("Data saved successfully!")
            const confirmation = await response.json()
            console.log(confirmation)
        } else {
            console.error(`Send failed: ${response.status}`)
        }

    } catch (e) {
        console.error("Send error", e)
    }
}

// Simulate a send
sendTelemetry(24.5)
Copied!

JSON.stringify() converts an object into JSON text. DeviceScript has this function built-in, so we don’t need to build the string manually.

Limits to keep in mind

We are still working on a microcontroller, not a server. There are limits.

RAM Memory

When doing await response.json(), the entire object must fit in the device’s RAM.

  • If the API returns a list of 5000 items, the ESP32 will run out of memory (OOM) and restart.
  • Solution: If you control the API, apply pagination or filtering to receive only the necessary fields. DeviceScript is not suitable for processing megabytes of JSON.
HTTPS and TLS

DeviceScript supports HTTPS, but TLS consumes CPU and a significant portion of memory. Additionally, it can only maintain one open socket at a time.

Timeout

Requests can take time. If the server is slow, the async function will remain suspended on await. Design a timeout and an error strategy consistent with the application’s logic.

Example: querying a weather API

To wrap up, a common challenge. What’s the weather like? We can query a weather API to decide whether to turn on an LED. You will need your own provider API key.

Note: This is a conceptual outline.

// Logical outline
const API_KEY = "YOUR_API_KEY"
const CITY = "Madrid"
const URL = `https://api.openweathermap.org/data/2.5/weather?q=${CITY}&appid=${API_KEY}`

async function checkWeather() {
    const res = await fetch(URL)
    const data = await res.json()
    
    // Access the deep structure of the JSON
    const weather = data.weather[0].main // E.g., "Rain", "Clear"
    
    if (weather === "Rain") {
        console.log("It's raining, closing the awning.")
        // await awningMotor.start()
    }
}
Copied!