HTTP API — Code Examples

All examples communicate with the Virtuino Cloud HTTP REST API using the VirtuinoCloud Arduino library — a lightweight, open-source wrapper that handles HTTPS, JSON serialization and multi-board support so your sketches stay short and readable.

Each example sends or receives data over HTTPS to https://api.virtuino.com/api/data. If you prefer to call the API directly without a library, see the HTTP API Guide.

Supports ESP32, ESP8266, Uno WiFi Rev2, MKR WiFi 1010, Nano 33 IoT and Nano RP2040 Connect. The correct HTTP backend is selected automatically at compile time — no configuration needed.

iliaslamprou/VirtuinoCloud on GitHub

Installation

Arduino IDE → Sketch → Include Library → Manage Libraries
Search for VirtuinoCloud and click Install.
In the same Library Manager, search for ArduinoJson by Benoit Blanchon and install it. Required on all boards.
WiFiNINA boards only (Uno WiFi Rev2, MKR WiFi 1010, Nano 33 IoT, Nano RP2040): also install ArduinoHttpClient by Arduino.
ESP8266 only: make sure your board core is version 3.x.
Tools → Board → Boards Manager → search esp8266 by ESP8266 Community → update.
If the Boards Manager URL is missing, add it in File → Preferences:
https://arduino.esp8266.com/stable/package_esp8266com_index.json
Console setup (required before uploading any sketch):
1. Log in at virtuino.com
2. Console → Fields — create every field used in the example, naming each one with its full topic
3. Console → Keys & Sub Users — copy your API key (it must be Read & Write to upload values)
In your sketch, use each field's full name exactly as it appears in the Console — for example weather/temperature.

Supported Boards

BoardWiFi chipWiFi includeExtra library
ESP32 (all variants)built-in#include <WiFi.h>none
ESP8266 — NodeMCU, Wemos D1 Mini, etc.built-in#include <ESP8266WiFi.h>none
Uno WiFi Rev2NINA-W102#include <WiFiNINA.h>ArduinoHttpClient
MKR WiFi 1010NINA-W102#include <WiFiNINA.h>ArduinoHttpClient
Nano 33 IoTNINA-W102#include <WiFiNINA.h>ArduinoHttpClient
Nano RP2040 ConnectNINA-W102#include <WiFiNINA.h>ArduinoHttpClient

API Reference

Every sketch that includes VirtuinoCloud.h has access to all of the following methods.

Read latest value — GET /api/data/field/{field name}?latest=true
VirtuinoResult r = cloud.read("weather/temperature")
Fetches the most recent stored value of one field, by its full name. Always check r.ok before using the result.
r.ok
bool — false on network error, wrong API key, unknown field, or a field with no value yet.
r.asFloat()  ·  r.asInt()
Returns the value cast to float or int. e.g. 23.4 / 23
r.asString()
Returns the raw value as an Arduino String. e.g. "23.4"
r.time
char[] — ISO 8601 UTC timestamp. e.g. "2024-06-15T14:30:00Z"
r.asJson()
Returns a compact JSON String: {"value":"23.4","time":"2024-06-15T14:30:00Z"}
Read history — GET /api/data/field/{field name}?limit={n}
String h = cloud.readHistory("weather/temperature", count)
Returns a JSON array String with the last count records, newest first. Returns "[]" on error.
[{"time":"2024-06-15T14:30:00Z","value":"23.4","source":"HTTP"},
{"time":"2024-06-15T14:29:00Z","value":"23.1","source":"HTTP"},...]
Parse with ArduinoJson. Keep count ≤ 50 on ESP8266. Server max: 5000.
Write single field — POST /api/data/write
bool ok = cloud.write("weather/temperature", value)
Uploads one numeric value. Returns true only when the value was stored — false on no connection, an error, or a field that does not exist.
bool ok = cloud.write("weather/temperature", value, true)
publish=true also pushes to the MQTT broker — live dashboard widgets update instantly (Essential+ plan).
bool ok = cloud.write("weather/temperature", value, true, "2024-06-15T14:30:00Z")
4th argument: explicit ISO 8601 UTC timestamp. Omit to use server arrival time.
Block write — multiple fields in ONE HTTP request — POST /api/data/write
cloud.beginWrite("weather")
cloud.beginWrite()
Starts a new block and clears any previously queued fields. The optional path is joined to every field with a / — add("temperature", …) writes to weather/temperature. With no path, give full names to add().
cloud.add(field, value)
cloud.add(field, value, true)
Queues a field. Does not send yet. true = publish to MQTT. Max 16 fields per block.
cloud.add("time", "2024-06-15T14:30:00Z")
Optional: sets a shared ISO 8601 timestamp for all fields in the block.
bool ok = cloud.send()
Sends all queued fields in ONE POST. Returns true only when every field was stored — if one does not exist, the others are stored and it returns false. Resets the queue so beginWrite can be called next loop.
Board name & diagnostics
cloud.setClientId("esp32-kitchen")
Optional. Sent with every write, so the board appears under this name in My Virtuino World. 1–64 characters: A-Z a-z 0-9 . _ : -
int code = cloud.lastStatus()
HTTP status of the last request: 200 OK · 404 no field with that name (after a write: a field was not stored — create it in Console → Fields) · 403 read-only or wrong API key · 429 too many writes · 0 or negative = no connection. The Debug Monitor in the Console shows the same requests live.
Every method also accepts the path and the field name separately — cloud.write("weather", "temperature", 23.4) writes to weather/temperature, exactly like the full name.

Examples

Example 1
Hello World — write a single field

Start here. No sensor and no wiring needed — the sketch makes up a value around 25 and sends it every 3 seconds, so a gauge or chart on your dashboard starts moving straight away. Fill in your WiFi and API key, upload, and you are done.

write()
Console setup: the sketch writes to the field demo_device/demo_field_1, which every new account already has. If yours does not, create it in Console → Fields with that exact name and the sketch runs unmodified.
ESP32 / ESP8266
WiFiNINA
#if defined(ESP32)
  #include <WiFi.h>          // ESP32
#else
  #include <ESP8266WiFi.h>   // ESP8266
#endif
#include <VirtuinoCloud.h>

const char* SSID     = "YOUR_WIFI_SSID";
const char* PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_KEY  = "YOUR_API_KEY";    // Console → Keys & Sub Users

VirtuinoCloud cloud(API_KEY);

void setup() {
    Serial.begin(115200);
    cloud.setClientId("hello-board");     // optional: the name in My Virtuino World
    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWiFi connected");
}

void loop() {
    float sensor_value = 25 + random(-5, +5);   // replace with a real sensor read

    // The field's full name, exactly as in Console → Fields
    bool ok = cloud.write("demo_device/demo_field_1", sensor_value);
    if (ok) {
        Serial.print("Uploaded "); Serial.println(sensor_value);
    } else {
        // 404 = no field with that name, 403 = read-only or wrong API key, 0 = no connection
        Serial.print("Upload failed — HTTP "); Serial.println(cloud.lastStatus());
    }
    delay(3000);   // upload every 3 seconds
}
#include <WiFiNINA.h>       // WiFiNINA boards — use this instead of <WiFi.h>
#include <VirtuinoCloud.h>  // also install ArduinoHttpClient from Library Manager

const char* SSID     = "YOUR_WIFI_SSID";
const char* PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_KEY  = "YOUR_API_KEY";

VirtuinoCloud cloud(API_KEY);

void setup() {
    Serial.begin(115200);
    cloud.setClientId("hello-nano");      // optional: the name in My Virtuino World
    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWiFi connected");
}

void loop() {
    float sensor_value = 25 + random(-5, +5);   // replace with a real sensor read

    bool ok = cloud.write("demo_device/demo_field_1", sensor_value);
    if (ok) {
        Serial.print("Uploaded "); Serial.println(sensor_value);
    } else {
        Serial.print("Upload failed — HTTP "); Serial.println(cloud.lastStatus());
    }
    delay(3000);
}
Example 2
Block write — multiple fields in one request

Uploads three sensor values (temperature, humidity, pressure) in a single HTTP POST using beginWrite / add / send. Without block write, three fields would require three separate requests and three times the network latency.

beginWrite / add / send
Console setup: Create the fields weather/temperature, weather/humidity, weather/pressure.
Extra library: DHT sensor library by Adafruit (Library Manager).
ESP32 / ESP8266
WiFiNINA
#if defined(ESP32)
  #include <WiFi.h>          // ESP32
#else
  #include <ESP8266WiFi.h>   // ESP8266
#endif
#include <DHT.h>
#include <VirtuinoCloud.h>

const char* SSID     = "YOUR_WIFI_SSID";
const char* PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_KEY  = "YOUR_API_KEY";

#if defined(ESP32)
  #define DHT_PIN 4
#else
  #define DHT_PIN D4
#endif
DHT dht(DHT_PIN, DHT22);

VirtuinoCloud cloud(API_KEY);

void setup() {
    Serial.begin(115200);
    dht.begin();
    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWiFi connected");
}

void loop() {
    float temperature = dht.readTemperature();
    float humidity    = dht.readHumidity();
    float pressure    = 1013.0;   // replace with a real barometer read

    if (isnan(temperature) || isnan(humidity)) {
        Serial.println("DHT22 read failed"); delay(5000); return;
    }

    // beginWrite() starts a new block — does NOT send yet.
    // "weather" is the path: it is joined to every field below with a "/",
    // so the values go to weather/temperature, weather/humidity, weather/pressure.
    cloud.beginWrite("weather");

    // add() queues a field value
    // publish=true → also push to MQTT broker for live dashboard widgets
    cloud.add("temperature", temperature, true);
    cloud.add("humidity",    humidity,    true);
    cloud.add("pressure",    pressure);         // no publish for this one

    // Optional: set a shared timestamp for all fields in this block
    // cloud.add("time", "2024-06-15T14:30:00Z");

    // send() transmits all queued fields in ONE HTTP POST, then resets the queue
    bool ok = cloud.send();

    if (ok) Serial.printf("Uploaded — T:%.1f  H:%.1f  P:%.1f\n", temperature, humidity, pressure);
    else    Serial.printf("Upload failed — HTTP %d\n", cloud.lastStatus());

    delay(30000);
}
#include <WiFiNINA.h>
#include <DHT.h>
#include <VirtuinoCloud.h>

const char* SSID     = "YOUR_WIFI_SSID";
const char* PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_KEY  = "YOUR_API_KEY";

#define DHT_PIN 2
DHT dht(DHT_PIN, DHT22);

VirtuinoCloud cloud(API_KEY);

void setup() {
    Serial.begin(115200);
    dht.begin();
    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWiFi connected");
}

void loop() {
    float temperature = dht.readTemperature();
    float humidity    = dht.readHumidity();
    if (isnan(temperature) || isnan(humidity)) { delay(5000); return; }

    cloud.beginWrite("weather");                  // → weather/temperature, weather/humidity
    cloud.add("temperature", temperature, true);
    cloud.add("humidity",    humidity,    true);
    bool ok = cloud.send();
    if (ok) Serial.println("Uploaded");
    else  { Serial.print("Failed — HTTP "); Serial.println(cloud.lastStatus()); }
    delay(30000);
}
Example 3
Read latest value — control a relay

Reads the state of two relay fields from the cloud every 5 seconds and switches the corresponding GPIO pins. A Toggle widget on the dashboard writes 1 (ON) or 0 (OFF) to each field.

read() GPIO output
Console setup: Create the fields home/relay1 and home/relay2.
Dashboard: Add two Toggle widgets, one connected to each field.
ESP32 / ESP8266
WiFiNINA
#if defined(ESP32)
  #include <WiFi.h>          // ESP32
#else
  #include <ESP8266WiFi.h>   // ESP8266
#endif
#include <VirtuinoCloud.h>

const char* SSID     = "YOUR_WIFI_SSID";
const char* PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_KEY  = "YOUR_API_KEY";

#if defined(ESP32)
  const int RELAY1 = 26;   // GPIO connected to relay module 1 (HIGH = ON)
  const int RELAY2 = 27;   // GPIO connected to relay module 2
#else
  const int RELAY1 = D1;
  const int RELAY2 = D2;
#endif

VirtuinoCloud cloud(API_KEY);

void setup() {
    Serial.begin(115200);
    pinMode(RELAY1, OUTPUT);
    pinMode(RELAY2, OUTPUT);
    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWiFi connected");
}

void loop() {
    // Read relay 1 state set by the dashboard toggle widget
    VirtuinoResult r1 = cloud.read("home/relay1");
    if (r1.ok) {
        digitalWrite(RELAY1, r1.asInt() ? HIGH : LOW);
        Serial.print("relay1="); Serial.print(r1.asString());
        Serial.print("  at "); Serial.println(r1.time);   // ISO 8601 timestamp

        // r1.asJson() → {"value":"1","time":"2024-06-15T14:30:00Z"}
    }

    VirtuinoResult r2 = cloud.read("home/relay2");
    if (r2.ok) digitalWrite(RELAY2, r2.asInt() ? HIGH : LOW);

    // 200 with no value = the field exists but nothing was written to it yet
    if (!r1.ok && !r2.ok) Serial.printf("Read failed — HTTP %d\n", cloud.lastStatus());

    delay(5000);   // poll every 5 seconds
}
#include <WiFiNINA.h>
#include <VirtuinoCloud.h>

const char* SSID     = "YOUR_WIFI_SSID";
const char* PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_KEY  = "YOUR_API_KEY";

const int RELAY1 = 2;
const int RELAY2 = 3;

VirtuinoCloud cloud(API_KEY);

void setup() {
    Serial.begin(115200);
    pinMode(RELAY1, OUTPUT);
    pinMode(RELAY2, OUTPUT);
    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWiFi connected");
}

void loop() {
    VirtuinoResult r1 = cloud.read("home/relay1");
    if (r1.ok) digitalWrite(RELAY1, r1.asInt() ? HIGH : LOW);

    VirtuinoResult r2 = cloud.read("home/relay2");
    if (r2.ok) digitalWrite(RELAY2, r2.asInt() ? HIGH : LOW);

    if (!r1.ok && !r2.ok) { Serial.print("Read failed — HTTP "); Serial.println(cloud.lastStatus()); }
    delay(5000);
}
Example 4
Motor control from dashboard

Reads a speed setpoint (0–255) and a direction flag from the cloud every 2 seconds. Sets PWM on a motor driver (e.g. L298N) and a direction GPIO. A Slider widget on the dashboard controls the speed; a Toggle widget controls the direction.

read() PWM + direction
Console setup: Create the fields motor/speed (slider 0–255) and motor/direction (toggle 0/1).
ESP32 / ESP8266
WiFiNINA
#if defined(ESP32)
  #include <WiFi.h>          // ESP32
#else
  #include <ESP8266WiFi.h>   // ESP8266
#endif
#include <VirtuinoCloud.h>

const char* SSID     = "YOUR_WIFI_SSID";
const char* PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_KEY  = "YOUR_API_KEY";

#if defined(ESP32)
  const int PWM_PIN = 18;   // ENA pin on L298N (speed)
  const int DIR_PIN = 19;   // IN1 pin on L298N (direction)
#else
  const int PWM_PIN = D5;
  const int DIR_PIN = D6;
#endif

VirtuinoCloud cloud(API_KEY);

void setup() {
    Serial.begin(115200);
    pinMode(DIR_PIN, OUTPUT);
    // analogWrite() works on both boards: 0–255 (ESP32 core 2.x/3.x, ESP8266 core 3.x)
    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWiFi connected");
}

void loop() {
    // Read speed value (0–255) set by the slider widget on the dashboard
    VirtuinoResult spd = cloud.read("motor/speed");
    if (spd.ok) {
        int pwm = constrain(spd.asInt(), 0, 255);
        analogWrite(PWM_PIN, pwm);
        Serial.print("Speed: "); Serial.println(pwm);
    }

    // Read direction: 0 = forward, 1 = reverse
    VirtuinoResult dir = cloud.read("motor/direction");
    if (dir.ok) {
        digitalWrite(DIR_PIN, dir.asInt() ? HIGH : LOW);
        Serial.println(dir.asInt() ? "Direction: REVERSE" : "Direction: FORWARD");
    }

    delay(2000);   // check every 2 seconds
}
#include <WiFiNINA.h>
#include <VirtuinoCloud.h>

const char* SSID     = "YOUR_WIFI_SSID";
const char* PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_KEY  = "YOUR_API_KEY";

const int PWM_PIN = 9;
const int DIR_PIN = 8;

VirtuinoCloud cloud(API_KEY);

void setup() {
    Serial.begin(115200);
    pinMode(DIR_PIN, OUTPUT);
    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWiFi connected");
}

void loop() {
    VirtuinoResult spd = cloud.read("motor/speed");
    if (spd.ok) analogWrite(PWM_PIN, constrain(spd.asInt(), 0, 255));

    VirtuinoResult dir = cloud.read("motor/direction");
    if (dir.ok) digitalWrite(DIR_PIN, dir.asInt() ? HIGH : LOW);

    delay(2000);
}
Example 5
Upload with NTP timestamp

Syncs time from an NTP server and attaches an explicit ISO 8601 UTC timestamp to each block upload. Useful when you want precise control over the timestamp shown in charts, or when you are uploading historical data collected offline.

Block write NTP timestamp
Timestamp format: ISO 8601 UTC — 2024-06-15T14:30:00Z. If omitted, the server records the time of arrival.
Console setup: the fields weather/temperature and weather/humidity (the same as Example 2).
ESP32 / ESP8266
WiFiNINA
#if defined(ESP32)
  #include <WiFi.h>          // ESP32
#else
  #include <ESP8266WiFi.h>   // ESP8266
#endif
#include <time.h>            // for configTime() and strftime()
#include <VirtuinoCloud.h>

const char* SSID     = "YOUR_WIFI_SSID";
const char* PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_KEY  = "YOUR_API_KEY";

VirtuinoCloud cloud(API_KEY);

// Fills buf with the current UTC time in ISO 8601 format: "2024-06-15T14:30:00Z"
void getTimestamp(char* buf, size_t len) {
    time_t now = time(nullptr);
    strftime(buf, len, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
}

void setup() {
    Serial.begin(115200);
    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWiFi connected");

    // Sync time from NTP — waits until a valid epoch is received
    configTime(0, 0, "pool.ntp.org", "time.nist.gov");
    Serial.print("Syncing NTP");
    while (time(nullptr) < 1000000000UL) { delay(500); Serial.print("."); }
    Serial.println(" done");
}

void loop() {
    float temperature = 23.4;   // replace with sensor read
    float humidity    = 61.0;

    char ts[25];
    getTimestamp(ts, sizeof(ts));   // e.g. "2024-06-15T14:30:00Z"

    // All fields in this block share the same explicit timestamp
    cloud.beginWrite("weather");                 // → weather/temperature, weather/humidity
    cloud.add("temperature", temperature, true);
    cloud.add("humidity",    humidity,    true);
    cloud.add("time",        ts);                // attach the NTP timestamp
    bool ok = cloud.send();

    Serial.printf("%s at %s\n", ok ? "Uploaded" : "FAILED", ts);
    delay(60000);   // upload every minute
}
#include <WiFiNINA.h>
#include <VirtuinoCloud.h>

const char* SSID     = "YOUR_WIFI_SSID";
const char* PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_KEY  = "YOUR_API_KEY";

VirtuinoCloud cloud(API_KEY);

// WiFi.getTime() returns Unix epoch seconds (UTC) from the WiFiNINA module's NTP.
// Converts it to ISO 8601 "2024-06-15T14:30:00Z" without any time library.
void getTimestamp(char* buf, size_t len) {
    unsigned long ep = WiFi.getTime();
    unsigned long days = ep / 86400UL, secs = ep % 86400UL;
    // days since 1970-01-01 → year/month/day (civil-from-days algorithm)
    long z = (long)days + 719468L;
    long era = z / 146097L;
    unsigned long doe = (unsigned long)(z - era * 146097L);
    unsigned long yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
    long y = (long)yoe + era * 400L;
    unsigned long doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
    unsigned long mp = (5 * doy + 2) / 153;
    unsigned int d = doy - (153 * mp + 2) / 5 + 1;
    unsigned int m = mp < 10 ? mp + 3 : mp - 9;
    if (m <= 2) y++;
    snprintf(buf, len, "%04ld-%02u-%02uT%02lu:%02lu:%02luZ",
             y, m, d, secs / 3600UL, (secs / 60UL) % 60UL, secs % 60UL);
}

void setup() {
    Serial.begin(115200);
    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWiFi connected");

    // The module needs a few seconds to get the time from NTP (0 until then)
    Serial.print("Waiting for time");
    while (WiFi.getTime() == 0) { delay(1000); Serial.print("."); }
    Serial.println(" done");
}

void loop() {
    char ts[25];
    getTimestamp(ts, sizeof(ts));                // e.g. "2024-06-15T14:30:00Z"
    cloud.beginWrite("weather");                 // → weather/temperature
    cloud.add("temperature", 23.4f, true);
    cloud.add("time", ts);
    Serial.print(cloud.send() ? "Uploaded at " : "Failed at "); Serial.println(ts);
    delay(60000);
}
Example 6
Read history & calculate average

Fetches the last 50 records of a field as a JSON array String, parses each entry with ArduinoJson and computes the average, minimum and maximum values. The same technique can be used to find anomalies, draw local charts, or make decisions based on trend data.

readHistory() ArduinoJson
Memory: Each record uses ~100 bytes of heap inside the JSON document. Keep count ≤ 50 on ESP8266 and ≤ 20 on WiFiNINA boards. ESP32 handles 500+ records comfortably.
Reads the field weather/temperature — upload some values first with Example 2 or 5.
ESP32 / ESP8266
WiFiNINA
#if defined(ESP32)
  #include <WiFi.h>          // ESP32
#else
  #include <ESP8266WiFi.h>   // ESP8266
#endif
#include <ArduinoJson.h>     // needed to parse the returned JSON array
#include <VirtuinoCloud.h>

const char* SSID     = "YOUR_WIFI_SSID";
const char* PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_KEY  = "YOUR_API_KEY";

VirtuinoCloud cloud(API_KEY);
bool fetched = false;

void setup() {
    Serial.begin(115200);
    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWiFi connected — fetching history...");
}

void loop() {
    if (!fetched) {
        // Returns: [{"time":"...","value":"23.4","source":"HTTP"}, ...]  (newest first)
        // Returns "[]" on error or if the field has no data yet
        String history = cloud.readHistory("weather/temperature", 50);

        // Parse with ArduinoJson — allocate ~100 bytes × number of records
        DynamicJsonDocument doc(8192);
        DeserializationError err = deserializeJson(doc, history);

        if (err) {
            Serial.print("JSON parse error: "); Serial.println(err.c_str());
        } else {
            JsonArray arr = doc.as<JsonArray>();
            float sum = 0, minVal = 1e9, maxVal = -1e9;
            int n = 0;

            for (JsonObject rec : arr) {
                float v = atof(rec["value"] | "0");
                sum    += v;
                minVal  = min(minVal, v);
                maxVal  = max(maxVal, v);
                Serial.printf("[%2d]  %s  →  %.2f\n", n, (const char*)rec["time"], v);
                n++;
            }

            if (n > 0) {
                Serial.println("────────────────────────────────");
                Serial.printf("Count: %d   Avg: %.2f   Min: %.2f   Max: %.2f\n",
                              n, sum/n, minVal, maxVal);
            } else if (cloud.lastStatus() != 200) {
                Serial.printf("Read failed — HTTP %d\n", cloud.lastStatus());
            } else {
                Serial.println("No records — upload some data first (Example 2 or 5)");
            }
        }
        fetched = true;
    }
    delay(1000);
}
#include <WiFiNINA.h>
#include <ArduinoJson.h>
#include <VirtuinoCloud.h>

const char* SSID     = "YOUR_WIFI_SSID";
const char* PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_KEY  = "YOUR_API_KEY";

VirtuinoCloud cloud(API_KEY);
bool fetched = false;

void setup() {
    Serial.begin(115200);
    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nConnected");
}

void loop() {
    if (!fetched) {
        // 20 records keep the JSON small enough for boards with little RAM
        String history = cloud.readHistory("weather/temperature", 20);
        DynamicJsonDocument doc(3072);
        if (!deserializeJson(doc, history)) {
            JsonArray arr = doc.as<JsonArray>();
            float sum = 0; int n = 0;
            for (JsonObject rec : arr) { sum += atof(rec["value"] | "0"); n++; }
            if (n > 0) {
                Serial.print("Avg: "); Serial.print(sum / n, 2);
                Serial.print(" over "); Serial.print(n); Serial.println(" records");
            }
        }
        fetched = true;
    }
    delay(1000);
}
Example 7
Thermostat — read setpoint & write back measurements

A complete closed-loop control example: reads a temperature setpoint from the dashboard, measures actual temperature with a DHT22, controls a heater relay using bang-bang logic with hysteresis, and uploads all measurements in one request. Demonstrates read() and beginWrite/add/send working together.

read() Block write Relay
Console setup: Create the fields thermostat/setpoint, thermostat/temperature, thermostat/heater_state.
Dashboard: Slider widget → thermostat/setpoint  |  Gauge → thermostat/temperature  |  LED → thermostat/heater_state
ESP32 / ESP8266
WiFiNINA
#if defined(ESP32)
  #include <WiFi.h>          // ESP32
#else
  #include <ESP8266WiFi.h>   // ESP8266
#endif
#include <DHT.h>
#include <VirtuinoCloud.h>

const char* SSID     = "YOUR_WIFI_SSID";
const char* PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_KEY  = "YOUR_API_KEY";

#if defined(ESP32)
  const int DHT_PIN = 4;
  const int HEATER  = 26;   // relay controlling the heater (HIGH = ON)
#else
  const int DHT_PIN = D4;
  const int HEATER  = D1;
#endif

DHT dht(DHT_PIN, DHT22);
VirtuinoCloud cloud(API_KEY);
float setpoint = 22.0f;     // kept between loops: a failed read keeps the last good value

void setup() {
    Serial.begin(115200);
    dht.begin();
    pinMode(HEATER, OUTPUT);
    cloud.setClientId("thermostat");     // optional: the name in My Virtuino World
    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWiFi connected");
}

void loop() {
    // Step 1 — Read desired setpoint from dashboard
    VirtuinoResult sp = cloud.read("thermostat/setpoint");
    if (sp.ok) setpoint = sp.asFloat();

    // Step 2 — Measure actual temperature
    float actual = dht.readTemperature();
    if (isnan(actual)) { Serial.println("DHT22 read failed"); delay(5000); return; }

    // Step 3 — Bang-bang control with 0.5°C hysteresis
    // Heater turns ON below (setpoint - 0.5) and OFF above (setpoint + 0.5)
    // No change inside the 1°C deadband — prevents rapid switching
    bool heaterOn = digitalRead(HEATER);
    if (actual < setpoint - 0.5f) heaterOn = true;
    if (actual > setpoint + 0.5f) heaterOn = false;
    digitalWrite(HEATER, heaterOn ? HIGH : LOW);

    // Step 4 — Upload measurements and heater state in ONE request
    // → thermostat/temperature, thermostat/heater_state
    // publish=true → live dashboard widgets update immediately
    cloud.beginWrite("thermostat");
    cloud.add("temperature",  actual,                  true);
    cloud.add("heater_state", heaterOn ? 1.0f : 0.0f, true);
    bool ok = cloud.send();

    Serial.printf("%s  SP:%.1f  Actual:%.1f  Heater:%s\n",
                  ok ? "OK  " : "FAIL", setpoint, actual, heaterOn ? "ON" : "OFF");
    delay(10000);   // control loop runs every 10 seconds
}
#include <WiFiNINA.h>
#include <DHT.h>
#include <VirtuinoCloud.h>

const char* SSID     = "YOUR_WIFI_SSID";
const char* PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_KEY  = "YOUR_API_KEY";

const int DHT_PIN = 2;
const int HEATER  = 4;

DHT dht(DHT_PIN, DHT22);
VirtuinoCloud cloud(API_KEY);
float setpoint = 22.0f;

void setup() {
    Serial.begin(115200);
    dht.begin();
    pinMode(HEATER, OUTPUT);
    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWiFi connected");
}

void loop() {
    VirtuinoResult sp = cloud.read("thermostat/setpoint");
    if (sp.ok) setpoint = sp.asFloat();

    float actual = dht.readTemperature();
    if (isnan(actual)) { delay(5000); return; }

    bool heaterOn = digitalRead(HEATER);
    if (actual < setpoint - 0.5f) heaterOn = true;
    if (actual > setpoint + 0.5f) heaterOn = false;
    digitalWrite(HEATER, heaterOn ? HIGH : LOW);

    cloud.beginWrite("thermostat");
    cloud.add("temperature",  actual,                  true);
    cloud.add("heater_state", heaterOn ? 1.0f : 0.0f, true);
    cloud.send();
    delay(10000);
}
Example 8
Multi-sensor weather station

A complete weather station sketch that combines multiple sensors: a DHT22 for temperature and humidity, a BMP280 for barometric pressure and altitude, and a photoresistor for light level. All six values are sent in one block write every minute. Also reads an alarm threshold from the dashboard and activates a buzzer if the temperature exceeds it.

Block write (6 fields) read() alarm Buzzer
Console setup: Create the fields station/temperature, station/humidity, station/pressure, station/altitude, station/light, station/alarm_threshold.
Extra libraries: DHT sensor library (Adafruit) · Adafruit BMP280 library.
ESP32 / ESP8266
WiFiNINA
#if defined(ESP32)
  #include <WiFi.h>          // ESP32
#else
  #include <ESP8266WiFi.h>   // ESP8266
#endif
#include <DHT.h>
#include <Adafruit_BMP280.h>
#include <VirtuinoCloud.h>

const char* SSID     = "YOUR_WIFI_SSID";
const char* PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_KEY  = "YOUR_API_KEY";

#if defined(ESP32)
  const int DHT_PIN   = 4;
  const int LIGHT_PIN = 34;       // ADC pin for photoresistor (0–4095)
  const float LIGHT_MAX = 4095.0f;
  const int BUZZER    = 25;       // alarm buzzer
#else
  const int DHT_PIN   = D4;
  const int LIGHT_PIN = A0;       // the only ADC pin on ESP8266 (0–1023)
  const float LIGHT_MAX = 1023.0f;
  const int BUZZER    = D5;
#endif

DHT dht(DHT_PIN, DHT22);
Adafruit_BMP280 bmp;        // I2C: SDA=21, SCL=22 on ESP32 · SDA=D2, SCL=D1 on ESP8266

VirtuinoCloud cloud(API_KEY);

void setup() {
    Serial.begin(115200);
    dht.begin();
    pinMode(BUZZER, OUTPUT);
    cloud.setClientId("weather-station");   // optional: the name in My Virtuino World

    if (!bmp.begin(0x76)) {   // try 0x77 if 0x76 doesn't work
        Serial.println("BMP280 not found — check wiring");
    }

    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWiFi connected");
}

void loop() {
    // ── Read all sensors ─────────────────────────────────────────────────────
    float temperature = dht.readTemperature();
    float humidity    = dht.readHumidity();
    float pressure    = bmp.readPressure() / 100.0f;             // Pa → hPa
    float altitude    = bmp.readAltitude(1013.25f);              // sea-level pressure hPa
    float light       = analogRead(LIGHT_PIN) * 100.0f / LIGHT_MAX;   // → 0–100 %

    if (isnan(temperature) || isnan(humidity)) {
        Serial.println("DHT22 read failed"); delay(5000); return;
    }

    // ── Check alarm threshold from dashboard ──────────────────────────────────
    VirtuinoResult threshold = cloud.read("station/alarm_threshold");
    if (threshold.ok && temperature > threshold.asFloat()) {
        tone(BUZZER, 1000, 500);   // beep for 500 ms
        Serial.printf("ALARM! Temp %.1f°C exceeds threshold %.1f°C\n",
                      temperature, threshold.asFloat());
    }

    // ── Upload all 5 sensor values in one HTTP request ────────────────────────
    // "station" is the path → station/temperature, station/humidity, ...
    cloud.beginWrite("station");
    cloud.add("temperature", temperature, true);
    cloud.add("humidity",    humidity,    true);
    cloud.add("pressure",    pressure,    true);
    cloud.add("altitude",    altitude);
    cloud.add("light",       light,       true);
    bool ok = cloud.send();

    Serial.printf("%s T:%.1f H:%.1f P:%.1f Alt:%.0f Light:%.0f%%\n",
                  ok ? "OK" : "FAIL",
                  temperature, humidity, pressure, altitude, light);

    delay(60000);   // upload every minute
}
#include <WiFiNINA.h>
#include <DHT.h>
#include <Adafruit_BMP280.h>
#include <VirtuinoCloud.h>

const char* SSID     = "YOUR_WIFI_SSID";
const char* PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_KEY  = "YOUR_API_KEY";

const int DHT_PIN = 2;
const int BUZZER  = 5;

DHT dht(DHT_PIN, DHT22);
Adafruit_BMP280 bmp;

VirtuinoCloud cloud(API_KEY);

void setup() {
    Serial.begin(115200);
    dht.begin();
    pinMode(BUZZER, OUTPUT);
    if (!bmp.begin(0x76)) Serial.println("BMP280 not found");
    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWiFi connected");
}

void loop() {
    float temperature = dht.readTemperature();
    float humidity    = dht.readHumidity();
    float pressure    = bmp.readPressure() / 100.0f;
    float altitude    = bmp.readAltitude(1013.25f);
    float light       = analogRead(A0) / 10.23f;      // 0–1023 → 0–100 %

    if (isnan(temperature) || isnan(humidity)) { delay(5000); return; }

    VirtuinoResult threshold = cloud.read("station/alarm_threshold");
    if (threshold.ok && temperature > threshold.asFloat())
        digitalWrite(BUZZER, HIGH);
    else
        digitalWrite(BUZZER, LOW);

    cloud.beginWrite("station");                     // → station/temperature, ...
    cloud.add("temperature", temperature, true);
    cloud.add("humidity",    humidity,    true);
    cloud.add("pressure",    pressure,    true);
    cloud.add("altitude",    altitude);
    cloud.add("light",       light,       true);
    Serial.println(cloud.send() ? "Uploaded" : "Failed");

    delay(60000);
}