The Nodrix Arduino library is out — 0.1.0, MIT-licensed, for ESP32 and ESP8266, available now in
the Arduino Library Manager and the PlatformIO registry. It’s optional, it sits on top of the same
open device protocol, and it turns roughly ninety lines of WiFi, TLS, JSON, and reconnect plumbing
into a handful. This post is why it exists.
Nodrix has always been deliberately SDK-free. A device authenticates with a token and speaks plain HTTPS or WebSocket, so a Raspberry Pi, a server script, or anything that can make a request talks to it with no broker and nothing to install. That’s a feature nearly everywhere — except on a microcontroller, where “just make an HTTPS request” quietly expands into WiFi management, a TLS socket, JSON parsing, protocol framing, acknowledgements, and reconnect handling.
It’s the story of the one place nodrix still felt like boilerplate, and the small library we wrote to fix it. If you just want the reference — install, API, transports, TLS — that lives in the Arduino library docs.
The sketch that looks fine and isn’t
Here’s the first thing most people write to drive an LED from the cloud. It connects, opens the socket, and reacts to the incoming message:
#include <WiFi.h>
#include <WebSocketsClient.h>
#include "secret.h"
WebSocketsClient ws;
void webSocketEvent(WStype_t type, uint8_t *payload, size_t length) {
if (type == WStype_TEXT) {
String msg = String((char*)payload);
if (msg.indexOf("\"value\":\"on\"") != -1 || msg.indexOf("\"value\":\"1\"") != -1)
digitalWrite(LED_BUILTIN, HIGH);
if (msg.indexOf("\"value\":\"off\"") != -1 || msg.indexOf("\"value\":\"0\"") != -1)
digitalWrite(LED_BUILTIN, LOW);
}
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(500);
ws.beginSSL(HOST, 443, ("/v1/control/ws?token=" + String(TOKEN)).c_str());
ws.onEvent(webSocketEvent);
}
void loop() { ws.loop(); }
It works on the bench, and it’s quietly broken in two ways.
It never acknowledges the command. Nodrix delivers control at-least-once — the cloud holds a write until the device confirms it, and re-sends anything outstanding on every reconnect. This sketch never acks, so the write is never “done”; it comes back every time the socket blips.
It string-matches the JSON. It only catches the string "on" or "1". But control values
arrive typed — a dashboard toggle can send the JSON boolean true or the number 1, and
"\"value\":\"1\"" matches neither. The LED silently ignores real writes. (There’s a third, quieter
issue: the cloud won’t even queue a write to led until that variable has been seen once — so on a
fresh project this handler may never fire at all.)
The sketch that’s correct — and 90 lines
Fix all of that properly and you get the version that actually belongs in production: parse the JSON, pull out the fields, drive the pin, and — the part everyone forgets — build and send the ack so the cloud stops re-delivering.
case WStype_TEXT: {
JsonDocument doc;
if (deserializeJson(doc, payload)) return;
if (String(doc["type"] | "") == "control") {
String id = doc["id"] | "";
String variable = doc["variable"] | "";
String value = doc["value"] | "";
if (variable == "led")
digitalWrite(LED_BUILTIN, (value == "on" || value == "1") ? HIGH : LOW);
JsonDocument ack; // hand-build the ack frame
ack["type"] = "ack";
ack["ids"].to<JsonArray>().add(id);
String out; serializeJson(ack, out);
ws.sendTXT(out);
}
break;
}
That’s just the message handler. Around it you still need the connect loop, beginSSL, a reconnect
interval, the CONNECTED/DISCONNECTED cases, and logging — the real sketch is about ninety lines. And
it still carries the typed-value bug: doc["value"] | "" coerces to a string, so a boolean true
becomes "" and the LED never turns on. The one line that is actually your project — “if led,
drive the pin” — is buried in plumbing that every device re-implements and every device gets a
corner of wrong.
The rule: your sketch should only hold your logic
That’s the line we drew. Everything that isn’t specific to your project — the transport, the JSON, the acks, the reconnects, the seeding, the type coercion — belongs to the library. Everything that is — which pin, which threshold, what to do when a value arrives — stays in your sketch. Held to that rule, the whole thing above becomes:
NODRIX_WRITE("led") {
digitalWrite(LED_PIN, value.asBool() ? HIGH : LOW);
Nodrix.send("led", value.asBool());
}
The socket, the parse, the ack, and the reconnect are gone — handled underneath. You call
Nodrix.begin(...) once and Nodrix.run() in the loop, and that’s the only “boilerplate” left — the
same shape as Blynk.run(). And that value.asBool() is quietly fixing the bug both hand-written
versions share: it coerces a JSON boolean, a number, and the string forms, so a toggle works no
matter which the dashboard sends.
And that’s one variable
Everything above is a single on/off LED. Real projects aren’t. A smart-home board switches four relays; a plant watcher reads a sensor and drives a pump; a fleet is a dozen of each. In the hand-written world every variable is another branch to parse, another id to ack, another value to seed and echo — the ninety lines don’t stay ninety, they grow with the project. With the library each variable is still just one block:
NODRIX_WRITE("light_living") { digitalWrite(LIVING, value.asBool()); }
NODRIX_WRITE("light_bedroom") { digitalWrite(BEDROOM, value.asBool()); }
NODRIX_WRITE("fan") { digitalWrite(FAN, value.asBool()); }
It gets sharper the moment a project needs both transports. Some devices are mains-powered and
hold a socket open for instant control; others run on a battery — wake, report, sleep. Hand-rolled,
those are two different programs: a WebSocket event loop and an HTTP poll-fetch-ack cycle, each with
its own parse, ack, and reconnect. With the library it’s the same handlers and a one-line switch —
Nodrix.begin(...) for the always-on board, Nodrix.beginHTTP(...) for the sleeper. You write the
project once; the transport is a detail. That’s the number to picture: not ninety lines saved once,
but ninety-plus lines you don’t write in every sketch, for every variable, on every device.
Why each piece is in the library
None of what it absorbs is exotic — typed-value coercion, automatic acks, control-variable seeding, reconnect and heartbeat, multi-WiFi failover, telemetry batching. They’re the corners you only find after the device has run for a week: the ack you forgot, so the cloud re-delivers the write forever; the variable the cloud won’t accept until it’s been seen once; the second WiFi network a roaming board needs when the router reboots. Each is small on its own and collectively the difference between a demo and something that survives a month unattended — which is why they belong in the library, written once, instead of rediscovered in every sketch. The reference docs show what each one does.
Why it’s C++ only, and stays optional
Two decisions people ask about.
Why no Python or Node SDK? Because the boilerplate only ever piled up in one place. Off a microcontroller, talking to nodrix is already a single HTTPS request or one WebSocket — a wrapper would save almost nothing and add a dependency to maintain. The pain was specific to embedded C++, so the fix is too. There is deliberately no plan for other-language SDKs.
Why keep it optional? Because the platform’s whole point is that you own it, with no lock-in, and a mandatory SDK quietly walks that back. The device protocol stays plain, open HTTPS and WebSocket — the naive sketch at the top of this post still works, warts and all. The library is a convenience over a protocol you could always speak by hand, not a gate in front of it. That’s the same reason it never became the headline: the hero is your own IoT cloud on Cloudflare; the library is just the shortest path onto it from an ESP32.
Get it
The library is at 0.1.0 and focused on doing one job well. If you build with ESP32 or ESP8266, the
fastest way to feel the difference is to flash the
LedControl example
and bind a toggle to led.
- Reference: the Arduino library docs — install, API, transports, TLS.
- Source & examples: github.com/decoded-cipher/nodrix-sdk.
The plumbing is finally somebody else’s problem.