Code Examples

Ready-to-use sketches for ESP32 and ESP8266. Each example is self-contained — copy, fill in your credentials, and upload.

Before you start: Create your fields in Console → Fields, naming each one with its full topic (for example esp32/temperature). Get your API key from Console → Keys & Sub Users. All examples use base URL https://api.virtuino.com/api/data.
Example 1
Read relay states from Virtuino Cloud
GET GPIO JSON

Reads the latest value of the field esp32/relay1 from the cloud and applies it to a GPIO pin. The value stored is "1" (ON) or "0" (OFF) — sent from the Virtuino app or any HTTP client.

Put the field's full name in the URL — /field/esp32/relay1 — and add latest=true to get only the most recent value instead of the full history. The API key is passed as the x-api-key header — this keeps the URL clean and reusable.
ESP32
ESP8266
// Required libraries — install via Arduino IDE → Library Manager:
//   ArduinoJson  (by Benoit Blanchon)
// WiFi and HTTPClient are built-in for ESP32.
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

// ── Configuration ────────────────────────────────────────────────────
const char* ssid       = "YOUR_WIFI_SSID";
const char* password   = "YOUR_WIFI_PASSWORD";
const char* apiKey     = "YOUR_API_KEY";
const char* fieldUrl   = "https://api.virtuino.com/api/data/field/esp32/relay1?latest=true";
// "esp32/relay1" = the field's full name, exactly as in Console → Fields.

const int RELAY_PIN = 26;  // GPIO pin connected to the relay module

void readRelayState() {
    if (WiFi.status() != WL_CONNECTED) return;
    HTTPClient http;
    http.begin(fieldUrl);
    http.addHeader("x-api-key", apiKey);
    int code = http.GET();
    String payload = http.getString();
    http.end();

    Serial.println("Read → HTTP " + String(code));
    if (code != 200) { Serial.println("Error: " + payload); return; }  // 404 = no field with that name

    StaticJsonDocument<512> doc;
    if (deserializeJson(doc, payload)) { Serial.println("JSON error"); return; }

    // Response: { "field": "esp32/relay1", "latest_entry": { "time": "…", "value": "1", "source": "…" } }
    const char* value = doc["latest_entry"]["value"];
    if (!value) { Serial.println("relay1 has no value yet"); return; }
    int state = String(value).toInt();
    digitalWrite(RELAY_PIN, state);  // 1 = relay ON, 0 = relay OFF
    Serial.printf("relay1 = %d\n", state);
}

void setup() {
    Serial.begin(115200);
    pinMode(RELAY_PIN, OUTPUT);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWi-Fi connected");
}

void loop() {
    static unsigned long last = 0;
    if (millis() - last > 5000) {  // poll cloud every 5 seconds
        last = millis();
        readRelayState();
    }
}
// Required: ArduinoJson (Library Manager). Board core ≥ 2.5.0 required for setInsecure().
// Board URL: https://arduino.esp8266.com/stable/package_esp8266com_index.json
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>

const char* ssid       = "YOUR_WIFI_SSID";
const char* password   = "YOUR_WIFI_PASSWORD";
const char* apiKey     = "YOUR_API_KEY";
const char* fieldUrl   = "https://api.virtuino.com/api/data/field/esp8266/relay1?latest=true";

const int RELAY_PIN = D1;
WiFiClientSecure client;

void readRelayState() {
    if (WiFi.status() != WL_CONNECTED) return;
    client.setInsecure();
    HTTPClient http;
    http.begin(client, fieldUrl);
    http.addHeader("x-api-key", apiKey);
    int code = http.GET();
    String payload = http.getString();
    http.end();

    Serial.println("Read → HTTP " + String(code));
    if (code != 200) { Serial.println("Error: " + payload); return; }

    StaticJsonDocument<512> doc;
    if (deserializeJson(doc, payload)) { Serial.println("JSON error"); return; }

    const char* value = doc["latest_entry"]["value"];
    if (!value) { Serial.println("relay1 has no value yet"); return; }
    int state = String(value).toInt();
    digitalWrite(RELAY_PIN, state);
    Serial.printf("relay1 = %d\n", state);
}

void setup() {
    Serial.begin(115200);
    pinMode(RELAY_PIN, OUTPUT);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWi-Fi connected");
}

void loop() {
    static unsigned long last = 0;
    if (millis() - last > 5000) {
        last = millis();
        readRelayState();
    }
}
Example 2
Upload sensor values to Virtuino Cloud
POST MQTT publish JSON

Sends temperature and humidity readings to the platform. Each field in the data array can include "publish": true to also push the value to the MQTT broker in real time, so connected Virtuino app widgets update instantly without polling.

publish: true requires the Essential+ plan and the api_gateway feature enabled. Without it, the field is stored normally but the MQTT message is not sent. Rate limit: 600 writes / minute per API key.
ESP32
ESP8266
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

const char* ssid     = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* apiKey   = "YOUR_API_KEY";
const char* writeUrl = "https://api.virtuino.com/api/data/write";

void uploadSensors(float temp, float hum, float pressure) {
    if (WiFi.status() != WL_CONNECTED) return;
    HTTPClient http;
    http.begin(writeUrl);
    http.addHeader("Content-Type", "application/json");

    StaticJsonDocument<384> doc;
    doc["api_key"] = apiKey;
    doc["path"]    = "esp32";  // joined to each field: esp32/temperature, esp32/humidity, esp32/pressure

    JsonArray data = doc.createNestedArray("data");

    // publish:true → value is also forwarded to the MQTT broker immediately
    // publish:false (or omit) → value is only stored in the database
    JsonObject f1 = data.createNestedObject(); f1["field"] = "temperature"; f1["value"] = temp;     f1["publish"] = true;
    JsonObject f2 = data.createNestedObject(); f2["field"] = "humidity";    f2["value"] = hum;      f2["publish"] = true;
    JsonObject f3 = data.createNestedObject(); f3["field"] = "pressure";    f3["value"] = pressure; f3["publish"] = false; // store only, no MQTT

    String body;
    serializeJson(doc, body);

    // HTTPClient keeps only the response headers you ask for — before the request
    const char* keep[] = { "Retry-After" };
    http.collectHeaders(keep, 1);
    int code = http.POST(body);

    if (code == 429) {
        // Rate limit hit — read Retry-After header and wait
        int retryAfter = http.header("Retry-After").toInt();
        Serial.printf("Rate limited — retry after %d s\n", retryAfter);
    } else {
        // 200 = success; the reply lists any field that does not exist (skipped_fields)
        Serial.println("Upload → HTTP " + String(code) + " " + http.getString());
    }
    http.end();
}

void setup() {
    Serial.begin(115200);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWi-Fi connected");
}

void loop() {
    static unsigned long last = 0;
    if (millis() - last > 15000) {  // upload every 15 seconds
        last = millis();
        // Replace with real sensor readings (e.g. DHT22, BME280)
        uploadSensors(random(200,300)/10.0f, random(400,700)/10.0f, 1013.25f);
    }
}
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>

const char* ssid     = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* apiKey   = "YOUR_API_KEY";
const char* writeUrl = "https://api.virtuino.com/api/data/write";

WiFiClientSecure client;

void uploadSensors(float temp, float hum, float pressure) {
    if (WiFi.status() != WL_CONNECTED) return;
    client.setInsecure();
    HTTPClient http;
    http.begin(client, writeUrl);
    http.addHeader("Content-Type", "application/json");

    StaticJsonDocument<384> doc;
    doc["api_key"] = apiKey;
    doc["path"]    = "esp8266";  // esp8266/temperature, esp8266/humidity, esp8266/pressure

    JsonArray data = doc.createNestedArray("data");
    JsonObject f1 = data.createNestedObject(); f1["field"] = "temperature"; f1["value"] = temp;     f1["publish"] = true;
    JsonObject f2 = data.createNestedObject(); f2["field"] = "humidity";    f2["value"] = hum;      f2["publish"] = true;
    JsonObject f3 = data.createNestedObject(); f3["field"] = "pressure";    f3["value"] = pressure; f3["publish"] = false;

    String body;
    serializeJson(doc, body);
    int code = http.POST(body);
    Serial.println("Upload → HTTP " + String(code) + " " + http.getString());  // reply lists any missing field
    http.end();
}

void setup() {
    Serial.begin(115200);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWi-Fi connected");
}

void loop() {
    static unsigned long last = 0;
    if (millis() - last > 15000) {
        last = millis();
        uploadSensors(random(200,300)/10.0f, random(400,700)/10.0f, 1013.25f);
    }
}
Example 3
Control motor from Virtuino Dashboard
GET PWM / GPIO

Polls the field esp32/motor_speed from the cloud (0–100, set by a Slider widget in the Virtuino app) and maps it to a PWM output pin to control motor speed. A second field esp32/motor_dir (0 or 1) controls direction via two digital pins. One request to /path-history/esp32?latest=true returns the latest value of every field whose name starts with esp32/.

For faster response, reduce the polling interval or use MQTT instead — the broker pushes values instantly without polling. See the MQTT Broker Setup guide.
ESP32
ESP8266
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

const char* ssid       = "YOUR_WIFI_SSID";
const char* password   = "YOUR_WIFI_PASSWORD";
const char* apiKey     = "YOUR_API_KEY";
const char* historyUrl = "https://api.virtuino.com/api/data/path-history/esp32?latest=true";   // every esp32/… field

// Motor driver pins (e.g. L298N)
const int PWM_PIN = 18;   // ENA on L298N — controls speed
const int DIR_A   = 19;   // IN1
const int DIR_B   = 21;   // IN2

void applyMotorCommands(int speed, int dir) {
    // dir 0 = forward, dir 1 = reverse
    digitalWrite(DIR_A, dir == 0 ? HIGH : LOW);
    digitalWrite(DIR_B, dir == 0 ? LOW  : HIGH);
    // Map speed 0-100 → PWM 0-255
    analogWrite(PWM_PIN, map(speed, 0, 100, 0, 255));
    Serial.printf("Motor: speed=%d%% dir=%s\n", speed, dir == 0 ? "forward" : "reverse");
}

void fetchMotorCommands() {
    if (WiFi.status() != WL_CONNECTED) return;
    HTTPClient http;
    http.begin(historyUrl);
    http.addHeader("x-api-key", apiKey);
    int code = http.GET();
    String payload = http.getString();
    http.end();

    if (code != 200) { Serial.println("Error: " + payload); return; }

    // Every esp32/… field comes back with its value, time and source — heap buffer, grow it for many fields
    DynamicJsonDocument doc(2048);
    if (deserializeJson(doc, payload)) { Serial.println("JSON error — increase the buffer"); return; }

    int speed = 0, dir = 0;
    JsonArray fields = doc["fields"];
    for (JsonObject f : fields) {
        String name = (const char*)f["field"];
        int    val  = String((const char*)f["value"]).toInt();
        if (name == "esp32/motor_speed") speed = constrain(val, 0, 100);
        if (name == "esp32/motor_dir")   dir   = constrain(val, 0, 1);
    }
    applyMotorCommands(speed, dir);
}

void setup() {
    Serial.begin(115200);
    pinMode(DIR_A, OUTPUT); pinMode(DIR_B, OUTPUT);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWi-Fi connected");
}

void loop() {
    static unsigned long last = 0;
    if (millis() - last > 3000) {  // poll every 3 seconds
        last = millis();
        fetchMotorCommands();
    }
}
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>

const char* ssid       = "YOUR_WIFI_SSID";
const char* password   = "YOUR_WIFI_PASSWORD";
const char* apiKey     = "YOUR_API_KEY";
const char* historyUrl = "https://api.virtuino.com/api/data/path-history/esp8266?latest=true";   // every esp8266/… field

const int PWM_PIN = D5;
const int DIR_A   = D6;
const int DIR_B   = D7;

WiFiClientSecure client;

void fetchMotorCommands() {
    if (WiFi.status() != WL_CONNECTED) return;
    client.setInsecure();
    HTTPClient http;
    http.begin(client, historyUrl);
    http.addHeader("x-api-key", apiKey);
    int code = http.GET();
    String payload = http.getString();
    http.end();

    if (code != 200) { Serial.println("Error: " + payload); return; }

    // Every esp8266/… field comes back with its value, time and source — heap buffer, grow it for many fields
    DynamicJsonDocument doc(2048);
    if (deserializeJson(doc, payload)) { Serial.println("JSON error — increase the buffer"); return; }

    int speed = 0, dir = 0;
    for (JsonObject f : doc["fields"].as<JsonArray>()) {
        String name = (const char*)f["field"];
        int    val  = String((const char*)f["value"]).toInt();
        if (name == "esp8266/motor_speed") speed = constrain(val, 0, 100);
        if (name == "esp8266/motor_dir")   dir   = constrain(val, 0, 1);
    }
    digitalWrite(DIR_A, dir == 0 ? HIGH : LOW);
    digitalWrite(DIR_B, dir == 0 ? LOW  : HIGH);
    analogWrite(PWM_PIN, map(speed, 0, 100, 0, 255));
    Serial.printf("Motor: speed=%d%% dir=%d\n", speed, dir);
}

void setup() {
    Serial.begin(115200);
    pinMode(DIR_A, OUTPUT); pinMode(DIR_B, OUTPUT);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWi-Fi connected");
}

void loop() {
    static unsigned long last = 0;
    if (millis() - last > 3000) { last = millis(); fetchMotorCommands(); }
}
Example 4
Upload data with specific timestamp
POST JSON

Adds a "time" field to the POST body to store readings with a custom timestamp instead of the server's arrival time. Useful for batch uploads, offline buffering, or when the device has an RTC module. The format must be ISO 8601 UTC: YYYY-MM-DDTHH:MM:SSZ.

If "time" is omitted, the server automatically assigns the current UTC timestamp. When providing a custom time, make sure the device clock is synchronised (e.g. via NTP).
ESP32
ESP8266
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <time.h>  // built-in — provides NTP synchronisation on ESP32

const char* ssid     = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* apiKey   = "YOUR_API_KEY";
const char* writeUrl = "https://api.virtuino.com/api/data/write";

// Build an ISO 8601 UTC timestamp string from the current system time
String getIsoTimestamp() {
    struct tm t;
    getLocalTime(&t);
    char buf[25];
    strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", &t);
    return String(buf);
}

void uploadWithTimestamp(float temp, float hum) {
    if (WiFi.status() != WL_CONNECTED) return;
    HTTPClient http;
    http.begin(writeUrl);
    http.addHeader("Content-Type", "application/json");

    StaticJsonDocument<320> doc;
    doc["api_key"] = apiKey;
    doc["path"]    = "esp32";   // esp32/temperature, esp32/humidity
    doc["time"]    = getIsoTimestamp();  // e.g. "2026-06-13T14:30:00Z"

    JsonArray data = doc.createNestedArray("data");
    JsonObject f1 = data.createNestedObject(); f1["field"] = "temperature"; f1["value"] = temp;
    JsonObject f2 = data.createNestedObject(); f2["field"] = "humidity";    f2["value"] = hum;

    String body;
    serializeJson(doc, body);
    int code = http.POST(body);
    Serial.println("Upload → HTTP " + String(code));
    http.end();
}

void setup() {
    Serial.begin(115200);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWi-Fi connected");
    // Sync time via NTP (pool.ntp.org) — UTC offset 0
    configTime(0, 0, "pool.ntp.org");
    Serial.print("Syncing NTP");
    struct tm t;
    while (!getLocalTime(&t)) { delay(500); Serial.print("."); }
    Serial.println(" done");
}

void loop() {
    static unsigned long last = 0;
    if (millis() - last > 15000) {
        last = millis();
        uploadWithTimestamp(random(200,300)/10.0f, random(400,700)/10.0f);
    }
}
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>
#include <time.h>

const char* ssid     = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* apiKey   = "YOUR_API_KEY";
const char* writeUrl = "https://api.virtuino.com/api/data/write";

WiFiClientSecure client;

String getIsoTimestamp() {
    time_t now = time(nullptr);
    struct tm* t = gmtime(&now);
    char buf[25];
    strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", t);
    return String(buf);
}

void uploadWithTimestamp(float temp, float hum) {
    if (WiFi.status() != WL_CONNECTED) return;
    client.setInsecure();
    HTTPClient http;
    http.begin(client, writeUrl);
    http.addHeader("Content-Type", "application/json");

    StaticJsonDocument<320> doc;
    doc["api_key"] = apiKey;
    doc["path"]    = "esp8266";
    doc["time"]    = getIsoTimestamp();

    JsonArray data = doc.createNestedArray("data");
    JsonObject f1 = data.createNestedObject(); f1["field"] = "temperature"; f1["value"] = temp;
    JsonObject f2 = data.createNestedObject(); f2["field"] = "humidity";    f2["value"] = hum;

    String body;
    serializeJson(doc, body);
    int code = http.POST(body);
    Serial.println("Upload → HTTP " + String(code));
    http.end();
}

void setup() {
    Serial.begin(115200);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWi-Fi connected");
    configTime(0, 0, "pool.ntp.org");
    while (time(nullptr) < 1000000000UL) { delay(500); }  // wait for NTP sync
    Serial.println("NTP synced");
}

void loop() {
    static unsigned long last = 0;
    if (millis() - last > 15000) {
        last = millis();
        uploadWithTimestamp(random(200,300)/10.0f, random(400,700)/10.0f);
    }
}
Example 5
Read last 100 values from a sensor
GET JSON array

Fetches the 100 most recent records for a single field using the limit query parameter. The response contains a data array with each entry's timestamp and value — useful for local analysis, display on an OLED, or logging to SD card.

Increase the StaticJsonDocument size if you request more than ~50 records. Each record takes roughly 60–80 bytes. For 100 records, 8 KB is a safe allocation on ESP32. ESP8266 has less heap — keep it under 50 records or process records one by one with a streaming parser.
ESP32
ESP8266
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

const char* ssid      = "YOUR_WIFI_SSID";
const char* password  = "YOUR_WIFI_PASSWORD";
const char* apiKey    = "YOUR_API_KEY";
// Endpoint: /field/{full field name}?limit=N
const char* historyUrl = "https://api.virtuino.com/api/data/field/esp32/temperature?limit=100";

void readHistory() {
    if (WiFi.status() != WL_CONNECTED) return;
    HTTPClient http;
    http.begin(historyUrl);
    http.addHeader("x-api-key", apiKey);
    int code = http.GET();
    String payload = http.getString();
    http.end();

    Serial.println("History → HTTP " + String(code));
    if (code != 200) { Serial.println("Error: " + payload); return; }

    // 8 KB is enough for ~100 records; increase if needed
    DynamicJsonDocument doc(8192);
    if (deserializeJson(doc, payload)) { Serial.println("JSON error"); return; }

    int count = doc["count"];
    Serial.printf("Received %d records:\n", count);

    // Iterate all records and print timestamp + value
    JsonArray data = doc["data"];
    for (JsonObject rec : data) {
        const char* ts  = rec["time"];
        const char* val = rec["value"];
        Serial.printf("  %s  →  %s°C\n", ts, val);
    }
}

void setup() {
    Serial.begin(115200);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWi-Fi connected");
    readHistory();  // fetch once on boot
}

void loop() { }
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>

const char* ssid       = "YOUR_WIFI_SSID";
const char* password   = "YOUR_WIFI_PASSWORD";
const char* apiKey     = "YOUR_API_KEY";
// ESP8266 has less heap — keep limit ≤ 50 to avoid out-of-memory crashes
const char* historyUrl = "https://api.virtuino.com/api/data/field/esp8266/temperature?limit=50";

WiFiClientSecure client;

void readHistory() {
    if (WiFi.status() != WL_CONNECTED) return;
    client.setInsecure();
    HTTPClient http;
    http.begin(client, historyUrl);
    http.addHeader("x-api-key", apiKey);
    int code = http.GET();
    String payload = http.getString();
    http.end();

    Serial.println("History → HTTP " + String(code));
    if (code != 200) { Serial.println("Error: " + payload); return; }

    // 4 KB for ~50 records on ESP8266
    DynamicJsonDocument doc(4096);
    if (deserializeJson(doc, payload)) { Serial.println("JSON error"); return; }

    Serial.printf("Received %d records:\n", (int)doc["count"]);
    for (JsonObject rec : doc["data"].as<JsonArray>())
        Serial.printf("  %s  →  %s°C\n", (const char*)rec["time"], (const char*)rec["value"]);
}

void setup() {
    Serial.begin(115200);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWi-Fi connected");
    readHistory();
}

void loop() { }
Example 6
Read & Write values to Virtuino Cloud
GET + POST JSON

A typical feedback loop: reads the field esp32/setpoint, set by the user in the Virtuino dashboard, applies it locally (e.g. a target temperature for a thermostat), then writes back the actual measured value to esp32/actual_temp so the dashboard shows the real state. Both operations happen in the same cycle.

ESP32
ESP8266
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

const char* ssid       = "YOUR_WIFI_SSID";
const char* password   = "YOUR_WIFI_PASSWORD";
const char* apiKey     = "YOUR_API_KEY";
const char* writeUrl   = "https://api.virtuino.com/api/data/write";
const char* historyUrl = "https://api.virtuino.com/api/data/field/esp32/setpoint?latest=true";

float currentSetpoint = 22.0f;  // target temperature — updated from cloud

// ── Step 1: Read the setpoint written by the user in the Virtuino app ─
void readSetpoint() {
    if (WiFi.status() != WL_CONNECTED) return;
    HTTPClient http;
    http.begin(historyUrl);
    http.addHeader("x-api-key", apiKey);
    int code = http.GET();
    String payload = http.getString();
    http.end();

    if (code != 200) return;
    StaticJsonDocument<512> doc;
    if (deserializeJson(doc, payload)) return;

    // Response: { "field": "esp32/setpoint", "latest_entry": { "time": "…", "value": "22.5", "source": "…" } }
    const char* value = doc["latest_entry"]["value"];
    if (!value) return;  // no value stored yet — keep the current setpoint
    currentSetpoint = String(value).toFloat();
    Serial.printf("Setpoint from cloud: %.1f°C\n", currentSetpoint);
}

// ── Step 2: Write the actual measured temperature back to the cloud ───
void writeActualTemp(float actualTemp) {
    if (WiFi.status() != WL_CONNECTED) return;
    HTTPClient http;
    http.begin(writeUrl);
    http.addHeader("Content-Type", "application/json");

    StaticJsonDocument<256> doc;
    doc["api_key"] = apiKey;
    doc["path"]    = "esp32";   // → esp32/actual_temp
    JsonArray data = doc.createNestedArray("data");
    JsonObject f = data.createNestedObject();
    f["field"]   = "actual_temp";
    f["value"]   = actualTemp;
    f["publish"] = true;  // push to MQTT so dashboard widget updates in real time

    String body;
    serializeJson(doc, body);
    int code = http.POST(body);
    Serial.printf("Write actual_temp=%.1f → HTTP %d\n", actualTemp, code);
    http.end();
}

void setup() {
    Serial.begin(115200);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWi-Fi connected");
}

void loop() {
    static unsigned long last = 0;
    if (millis() - last > 10000) {
        last = millis();
        readSetpoint();
        // Replace with real sensor reading
        float measuredTemp = random(190, 310) / 10.0f;
        Serial.printf("Setpoint: %.1f°C  |  Actual: %.1f°C\n", currentSetpoint, measuredTemp);
        writeActualTemp(measuredTemp);
    }
}
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>

const char* ssid       = "YOUR_WIFI_SSID";
const char* password   = "YOUR_WIFI_PASSWORD";
const char* apiKey     = "YOUR_API_KEY";
const char* writeUrl   = "https://api.virtuino.com/api/data/write";
const char* historyUrl = "https://api.virtuino.com/api/data/field/esp8266/setpoint?latest=true";

float currentSetpoint = 22.0f;
WiFiClientSecure client;

void readSetpoint() {
    if (WiFi.status() != WL_CONNECTED) return;
    client.setInsecure();
    HTTPClient http;
    http.begin(client, historyUrl);
    http.addHeader("x-api-key", apiKey);
    int code = http.GET();
    String payload = http.getString();
    http.end();

    if (code != 200) return;
    StaticJsonDocument<512> doc;
    if (deserializeJson(doc, payload)) return;

    const char* value = doc["latest_entry"]["value"];
    if (!value) return;  // no value stored yet — keep the current setpoint
    currentSetpoint = String(value).toFloat();
    Serial.printf("Setpoint: %.1f°C\n", currentSetpoint);
}

void writeActualTemp(float actualTemp) {
    if (WiFi.status() != WL_CONNECTED) return;
    client.setInsecure();
    HTTPClient http;
    http.begin(client, writeUrl);
    http.addHeader("Content-Type", "application/json");

    StaticJsonDocument<256> doc;
    doc["api_key"] = apiKey;
    doc["path"]    = "esp8266";
    JsonArray data = doc.createNestedArray("data");
    JsonObject f = data.createNestedObject();
    f["field"] = "actual_temp"; f["value"] = actualTemp; f["publish"] = true;

    String body;
    serializeJson(doc, body);
    int code = http.POST(body);
    Serial.printf("Write actual_temp=%.1f → HTTP %d\n", actualTemp, code);
    http.end();
}

void setup() {
    Serial.begin(115200);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.println("\nWi-Fi connected");
}

void loop() {
    static unsigned long last = 0;
    if (millis() - last > 10000) {
        last = millis();
        readSetpoint();
        float measuredTemp = random(190, 310) / 10.0f;
        writeActualTemp(measuredTemp);
    }
}