Send data to App
To update a Display widget on the dashboard, your sketch calls a method on instant.<widget>("widget_id"). One line moves the needle of a gauge, fills a level bar, pushes a point on a chart.
instant.gauge("temp_gauge").setValue(22.5f);
That’s it for the call shape. The whole page is about when you call it — and how to keep your sketch from drowning the link.
Don’t push from loop() directly
The Arduino loop() function runs thousands of times per second. If you push a value to a widget on every iteration, you flood the link — your sketch ends up spending all its bandwidth shipping near-identical numbers. The dashboard won’t render any faster, and other widgets will lag.
The fix is a timer. The library ships an InstantTimer helper that calls a function at a fixed interval, no matter how fast your loop() is.
200–1000 ms is plenty for most dashboards. Faster than that doesn’t make a chart smoother — it just floods.
#include <InstantIoTWiFiAP.hpp>
#include <utils/InstantIoTTimer.hpp>
const char* AP_SSID = "InstantIoT-Greenhouse";
const char* AP_PASS = "12345678";
InstantIoTWiFiAP instant(AP_SSID, AP_PASS);
InstantTimer timers;
void pushTemperature() {
if (!instant.connected()) return;
float c = readTemperature();
instant.gauge("temp_gauge").setValue(c);
}
void setup() {
Serial.begin(115200);
instant.begin();
timers.every(500, pushTemperature);
}
void loop() {
instant.loop();
timers.run();
}
The if (!instant.connected()) return; line is a small piece of hygiene — when no phone is joined yet, there’s nothing to push to. Skipping the call avoids wasted work and keeps your serial logs clean.
Two ways to call a method
Both patterns below are equivalent. Pick the one that reads better for your sketch.
Inline — one-shot or rare updates:
instant.gauge("temp_gauge").setValue(c);
Cached reference — when you push to the same widget often, or to many widgets:
auto temp = instant.gauge("temp_gauge");
void pushTemperature() {
if (!instant.connected()) return;
temp.setValue(readTemperature());
}
The cached form is a convenience — it doesn’t hold any extra state, just spares you from retyping the widget ID. Use whichever feels right.
Method reference
| Widget | Methods |
|---|---|
| Metric | setValue(float), setSecondaryValue(const char*, const char*) |
| Text | setText(const char*) — max ~127 characters |
| LED | on(), off(), toggle(), setBrightness(int 0-100), setIntensity(float 0.0-1.0), setColor(uint8 r, uint8 g, uint8 b), setColor(uint32 0xRRGGBB), setState(bool, float) |
| Gauge | setValue(float), setRange(min, max), update(value, min, max) |
| Bar Chart | setValues(const float* arr, uint8 count), setBar(uint8 idx, float), clear() |
| Advanced Chart | addPoint(seriesId, y), addPoint(y), addTimedPoint(seriesId, x, y), clearSeries(seriesId), clear(), resetIndex() |
| Horizontal Level | setValue(float), setRange(min, max), update(value, min, max) |
| Vertical Level | setValue(float), setRange(min, max), update(value, min, max) |
Metric
Metric shows a single big number with optional label, unit, and a secondary line you can use for a peak, a target, or a trend.
auto cpu = instant.metric("cpu_load");
void pushCpu() {
if (!instant.connected()) return;
float load = readCpuLoad();
cpu.setValue(load);
static float peak = 0;
if (load > peak) peak = load;
cpu.setSecondaryValue(String(peak, 1).c_str(), "peak");
}
| Method | What it does |
|---|---|
setValue(float) | Update the main number. |
setSecondaryValue(const char* value, const char* label) | Set a secondary line below the main value. Both are free strings — format your number however you want. |
Text
Text displays whatever string your sketch sends.
auto status = instant.text("status_msg");
void pushStatus() {
if (!instant.connected()) return;
char buf[64];
snprintf(buf, sizeof(buf),
"Up %lus, %d events",
millis() / 1000, eventCount);
status.setText(buf);
}
| Method | What it does |
|---|---|
setText(const char* text) | Update the displayed string. Maximum ~127 characters. |
LED
LED is a glowing indicator with brightness, color, and effects. The methods can be called from a timer or directly from a WHEN_* event — they’re cheap.
auto led = instant.led("status_led");
void pushStatus() {
if (!instant.connected()) return;
if (isError()) led.setColor(0xFF0000); // red
else if (busy()) led.setColor(0xFFA500); // orange
else led.setColor(0x00FF00); // green
led.setBrightness(connected() ? 100 : 30);
}
| Method | What it does |
|---|---|
on() / off() / toggle() | Quick on/off control. |
setBrightness(int 0-100) | Brightness as a percentage. |
setIntensity(float 0.0-1.0) | Brightness as a 0..1 float. |
setColor(uint8 r, uint8 g, uint8 b) | Color from RGB components. |
setColor(uint32 0xRRGGBB) | Color from a hex value. |
setState(bool on, float intensity) | Set on/off and brightness in one call. |
Gauge
Gauge animates an arc to the value you push. You can set the value alone, change the range live, or update both atomically.
auto temp = instant.gauge("temp_gauge");
void pushTemperature() {
if (!instant.connected()) return;
float c = readTemperature();
temp.setValue(c);
// Or change the range live in one call:
// temp.update(c, -10.0f, 50.0f);
}
| Method | What it does |
|---|---|
setValue(float) | Update the displayed value (clamped to the current range). |
setRange(float min, float max) | Change the range live. |
update(float value, float min, float max) | Set value and range atomically. |
Bar Chart
Bar Chart shows N bars side by side. The order of values your sketch pushes must match the order of slots configured in the app.
auto bars = instant.barChart("env");
void pushEnv() {
if (!instant.connected()) return;
float values[3] = {
readTemp(),
readHumidity(),
readPressureNorm()
};
bars.setValues(values, 3);
// Or update one bar at a time:
// bars.setBar(0, readTemp());
}
| Method | What it does |
|---|---|
setValues(const float* arr, uint8 count) | Push the whole array at once. Order matches the slot order in the app. |
setBar(uint8 idx, float value) | Update one bar by 0-based index. |
clear() | Reset all bars to 0. |
Advanced Chart
Advanced Chart has two modes set in the app: Oscilloscope (X auto-incremented) and Time series (you push X explicitly).
auto chart = instant.chart("env_chart");
void pushChart() {
if (!instant.connected()) return;
// Oscilloscope mode: just push Y
chart.addPoint("temp", readTemp());
chart.addPoint("humidity", readHumidity());
// Time series mode: push X + Y
// chart.addTimedPoint("temp", (float)millis(), readTemp());
}
| Method | What it does |
|---|---|
addPoint(const char* seriesId, float y) | Append a point to a named series (Oscilloscope mode). |
addPoint(float y) | Shortcut for the default series. |
addTimedPoint(const char* seriesId, float x, float y) | Append a point with explicit X (Time series mode). |
clearSeries(const char* seriesId) | Empty a single series. |
clear() | Empty all series. |
resetIndex() | Reset Oscilloscope’s auto-X counter to 0. |
Horizontal Level / Vertical Level
Both Levels share the same API. The orientation lives in the app — your sketch doesn’t care.
auto tank = instant.hLevel("water_tank");
void pushTank() {
if (!instant.connected()) return;
float distanceCm = readUltrasonic();
float fillPct = constrain(map((int)distanceCm, 30, 5, 0, 100), 0, 100);
tank.setValue((float)fillPct);
}
For Vertical Level, swap hLevel for vLevel — same methods.
| Method | What it does |
|---|---|
setValue(float) | Update the bar value. |
setRange(float min, float max) | Change the range live. |
update(float value, float min, float max) | Set value and range atomically. |
Working example — multiple Displays at different rates
A single sketch pushes to a Gauge, a Horizontal Level, and a Bar Chart. Each gets its own timer at the rate that suits it: the gauge animates fast (200 ms), the level a bit faster (150 ms), the bar chart slower (1500 ms). The button and slider on the dashboard drive the same LED pin live.
Direct Mode
#include <InstantIoTWiFiAP.hpp>
#include <utils/InstantIoTTimer.hpp>
#include <math.h>
const char* AP_SSID = "InstantIoT-Greenhouse";
const char* AP_PASS = "12345678";
#define LED_PIN 2
InstantIoTWiFiAP instant(AP_SSID, AP_PASS);
InstantTimer timers;
bool buttonOn = false;
float sliderPct = 0.0f;
float gaugePhase = 0.0f;
float levelPhase = 0.0f;
float barPulse = 0.0f;
auto g1 = instant.gauge("gauge1");
auto level = instant.hLevel("level1");
auto bars = instant.barChart("bars1");
void applyPin2() {
float pct = buttonOn ? 100.0f : sliderPct;
analogWrite(LED_PIN, (int)(pct / 100.0f * 255.0f));
}
ISimpleButton("btn1") {
WHEN_TOGGLED(isOn) { buttonOn = isOn; applyPin2(); }
WHEN_PRESSED { buttonOn = true; applyPin2(); }
WHEN_RELEASED { buttonOn = false; applyPin2(); }
};
IHorizontalSlider("slider1") {
WHEN_CHANGING(v) { sliderPct = v; applyPin2(); }
WHEN_CHANGED(v) { sliderPct = v; applyPin2(); }
};
void pushGauge() {
if (!instant.connected()) return;
gaugePhase += 0.1f;
if (gaugePhase > TWO_PI) gaugePhase -= TWO_PI;
float v = (sinf(gaugePhase) + 1.0f) * 50.0f;
g1.update(v, 0.0f, 100.0f);
}
void pushLevel() {
if (!instant.connected()) return;
levelPhase += 1.5f;
if (levelPhase > 200.0f) levelPhase -= 200.0f;
float v = (levelPhase <= 100.0f) ? levelPhase : (200.0f - levelPhase);
level.update(v, 0.0f, 100.0f);
}
void pushBars() {
if (!instant.connected()) return;
barPulse = (barPulse > 95.0f) ? 5.0f : barPulse + 12.0f;
float values[5];
values[0] = (sinf(gaugePhase) + 1.0f) * 50.0f;
values[1] = (levelPhase <= 100.0f) ? levelPhase : (200.0f - levelPhase);
values[2] = sliderPct;
values[3] = buttonOn ? 100.0f : 0.0f;
values[4] = barPulse;
bars.setValues(values, 5);
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
instant.begin();
timers.every(200, pushGauge);
timers.every(150, pushLevel);
timers.every(1500, pushBars);
}
void loop() {
instant.loop();
timers.run();
}
Server Mode
Same sketch — the include and the constructor are the only lines that change. The widget code (macros, push functions, timers) is identical.
#include <InstantIoTWiFiServer.hpp>
#include <utils/InstantIoTTimer.hpp>
#include <math.h>
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const char* SERVER_IP = "192.168.1.42";
const uint16_t SERVER_PORT = 9001;
const char* DEVICE_TOKEN = "YOUR_DEVICE_TOKEN";
#define LED_PIN 2
InstantIoTWiFiServer instant(SERVER_IP, SERVER_PORT, DEVICE_TOKEN);
InstantTimer timers;
bool buttonOn = false;
float sliderPct = 0.0f;
float gaugePhase = 0.0f;
float levelPhase = 0.0f;
float barPulse = 0.0f;
auto g1 = instant.gauge("gauge1");
auto level = instant.hLevel("level1");
auto bars = instant.barChart("bars1");
void applyPin2() {
float pct = buttonOn ? 100.0f : sliderPct;
analogWrite(LED_PIN, (int)(pct / 100.0f * 255.0f));
}
ISimpleButton("btn1") {
WHEN_TOGGLED(isOn) { buttonOn = isOn; applyPin2(); }
WHEN_PRESSED { buttonOn = true; applyPin2(); }
WHEN_RELEASED { buttonOn = false; applyPin2(); }
};
IHorizontalSlider("slider1") {
WHEN_CHANGING(v) { sliderPct = v; applyPin2(); }
WHEN_CHANGED(v) { sliderPct = v; applyPin2(); }
};
void pushGauge() {
if (!instant.connected()) return;
gaugePhase += 0.1f;
if (gaugePhase > TWO_PI) gaugePhase -= TWO_PI;
float v = (sinf(gaugePhase) + 1.0f) * 50.0f;
g1.update(v, 0.0f, 100.0f);
}
void pushLevel() {
if (!instant.connected()) return;
levelPhase += 1.5f;
if (levelPhase > 200.0f) levelPhase -= 200.0f;
float v = (levelPhase <= 100.0f) ? levelPhase : (200.0f - levelPhase);
level.update(v, 0.0f, 100.0f);
}
void pushBars() {
if (!instant.connected()) return;
barPulse = (barPulse > 95.0f) ? 5.0f : barPulse + 12.0f;
float values[5];
values[0] = (sinf(gaugePhase) + 1.0f) * 50.0f;
values[1] = (levelPhase <= 100.0f) ? levelPhase : (200.0f - levelPhase);
values[2] = sliderPct;
values[3] = buttonOn ? 100.0f : 0.0f;
values[4] = barPulse;
bars.setValues(values, 5);
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
instant.begin(WIFI_SSID, WIFI_PASS);
timers.every(200, pushGauge);
timers.every(150, pushLevel);
timers.every(1500, pushBars);
}
void loop() {
instant.loop();
timers.run();
}
Notes
- The reference returned by
instant.<widget>("id")is cheap to recreate — the inline form and the cached form are equivalent at runtime. Cache when it makes the sketch easier to read. - Out-of-range values are clamped by the widget itself in the app — your sketch doesn’t need to validate before pushing.
- A Display widget with no value yet shows its default state. Pushing once on connection (e.g. inside
setup()afterinstant.begin(...)) gives the dashboard a starting point, but it’s optional.
Next
→ Receive command from App — The reverse direction.
→ Examples — Sketches putting Receive and Send together.
→ Manage Connections — How instant connects to phone or server.