Ready-to-use sketches for ESP32 and ESP8266. Each example is self-contained — copy, fill in your credentials, and upload.
esp32/temperature).
Get your API key from Console → Keys & Sub Users.
All examples use base URL https://api.virtuino.com/api/data.
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.
/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.
// 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(); } }
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.
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.
#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); } }
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/.
#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(); } }
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.
"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).
#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); } }
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.
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.
#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() { }
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.
#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); } }