# nodrix — full reference > nodrix is an open-source, single-tenant IoT cloud that deploys to your own Cloudflare account. Hardware speaks plain HTTPS or WebSocket, telemetry streams to realtime drag-and-drop dashboards, automations run at the edge, and a clean read API exposes the data — with no broker, no servers, and your data never leaving your account. Status: stable (v1.0). License: MIT. Home: https://nodrix.live/ Docs: https://nodrix.live/docs Widgets: https://nodrix.live/widgets Source: https://github.com/decoded-cipher/nodrix ## What nodrix is - An open-source IoT backend that deploys straight into the user's own Cloudflare account. - Single-tenant: every deployment is isolated in the owner's account, on their own D1, R2, and Durable Objects. - One-click "Deploy to Cloudflare" provisions Workers, Durable Objects, D1, R2, and KV. - Runs entirely on Cloudflare primitives — nothing to host, nothing to maintain. ## Features 1. Telemetry over HTTPS or WebSocket — Hardware POSTs JSON to the project, or opens a WebSocket (/v1/control/ws). Variables create themselves on first sight: no schema to declare, no MQTT broker to run, and no SDK required. Anything that can make an HTTPS request can talk to nodrix; on ESP32/ESP8266 an optional Arduino library wraps the whole protocol. 2. Realtime dashboards, two-way — Drop widgets onto a grid, bind them to variables, watch values stream live over hibernating WebSockets that cost nothing while idle. Toggles, sliders, and buttons write back to hardware on the same channel; devices ack when applied. 3. Automations without a server — A visual flow builder. Each automation is a graph: one or more triggers (variable threshold, a clock, sunrise/sunset, custom event, or manual run) flow through optional conditions (if-variable comparison that branches yes/no, time window) into actions (set a variable, call an integration, emit an event). Integrations cover HTTP service (with optional HMAC request signing), email, and chat (Slack, Telegram, Discord). All evaluated at the edge. 4. A clean read API — Edge-cached latest state, recent time-series, and variable listings behind one bearer token. Plug in Grafana, a React app, or a Raspberry Pi screen. 5. Single-tenant by design — Every deploy lands in the user's own account. Email + password out of the box, with optional Google or GitHub sign-in. 6. MCP server for AI clients — Optional, off by default, owner-gated. Two transports on the same worker: bearer-token at /v1/mcp (for CLI/IDE clients like Claude Code) and OAuth 2.1 at /v1/mcp/oauth (for browser-based clients like claude.ai connectors). Read tools (list/get projects, variables, dashboards, automations, integrations, state, series) are exposed when the server is on; management tools (create/update of projects, variables, dashboards, automations, integrations; run automations; set variable values) require an additional deployment-wide writes toggle AND an explicit mcp:manage scope at the consent step. No delete operations are ever exposed. Every write is recorded in the audit log when enabled, tagged with source=mcp so AI-initiated changes are distinguishable from web/API ones. ## How it works (four steps) 1. Deploy in one click — Hit "Deploy to Cloudflare". It provisions D1, R2, KV, and the Worker straight into the account. 2. Claim the instance — Open the worker URL and create the first account; it becomes the owner. Spin up the first project in a couple of clicks. 3. Connect hardware — Mint a project token and point the device at /v1/telemetry. Variables show up the moment data starts flowing. 4. Build & automate — Compose a dashboard, bind widgets to variables, and wire automations to act on the data — or hand it off through the read API. ## Device protocol (overview) - Send telemetry: POST /v1/telemetry with header "Authorization: Bearer " and a JSON body like { "metrics": { "temperature": 23.4, "humidity": 61 } }. Responds 204 No Content; variables are auto-created. - Read state: GET /v1/projects/:proj/state returns the latest value per variable, edge-cached. - Arduino/ESP library: an optional library for ESP32/ESP8266 wraps the protocol — handle control writes with NODRIX_WRITE("var"){…} and push telemetry with Nodrix.send(). Repo: https://github.com/decoded-cipher/nodrix-sdk ## Widgets (framework-agnostic Web Components) Display: - iot-value — The latest reading of a single variable, large and legible. Attributes: data-title, data-unit. - iot-gauge — An arc gauge for a numeric variable with configurable min/max bounds. Attributes: data-title, data-min, data-max, data-unit. - iot-percent — A circular percentage ring that maps a value to 0–100%, with optional color thresholds that recolor the ring by value. Attributes: data-title, data-min, data-max, data-unit. - iot-chart — A multi-series time-series chart (ApexCharts): line, area, bar, or stepline, with optional drag-to-zoom. Attributes: data-title, data-chart-type, data-zoom. - iot-map — Geographic markers from static coordinates or live lat/lng variables, on a configurable basemap. Attributes: data-title, data-basemap, data-zoom. Control: - iot-toggle — On/off switch that writes a value to a variable and reflects last reported state. Attributes: data-title, data-variable, data-on-value, data-off-value. - iot-slider — Horizontal slider for a numeric write; commits on release. Attributes: data-title, data-variable, data-min, data-max, data-step. - iot-push — Momentary push button for one-shot commands (restart a node, fire a routine, kick off a script). Attributes: data-title, data-variable, data-value, data-label. - iot-color — Color wheel for writing a color to a variable; drag for hue/saturation, set brightness, or tap a preset. Commits on release with hex/hsv/rgb output. Attributes: data-title, data-variable, data-format, data-brightness, data-hex-input, data-presets. ## Tech stack Cloudflare Workers, Durable Objects, D1, R2, and KV. ## Source & license - Repository: https://github.com/decoded-cipher/nodrix - Arduino/ESP library: https://github.com/decoded-cipher/nodrix-sdk - License: MIT # Guides (full text) The following are nodrix's hands-on guides in full — hardware builds, platform comparisons, and concept explainers, each with its FAQ. All builds report to a nodrix instance on the reader's own Cloudflare account. ## Guide: Cloudflare limits and what they cost you URL: https://nodrix.live/guides/cloudflare-free-tier-limits Category: concept What a nodrix deployment actually consumes on Cloudflare's free tier, which ceiling you hit first, and when the $5/month plan becomes necessary. **A hobby deployment runs free.** The ceiling you meet first is Workers requests — 100,000 a day, which is roughly eleven devices posting every ten seconds, or fifty posting every minute. Past that, Workers Paid is **$5/month flat** and stays that way for a long time. nodrix runs in your own Cloudflare account, so the bill is Cloudflare's rather than a per-device licence. That is the point of the architecture, but it does mean the platform's limits become yours. What follows is the arithmetic: what a deployment consumes, which ceiling arrives first, and when the five-dollar plan stops being optional. ## Where your data actually goes The limits only make sense alongside the storage split, because the obvious guess — that every reading becomes a database row — is wrong. | Store | Holds | Grows with | |---|---|---| | Project Durable Object (SQLite) | Current variable state, recent ring buffer, pending control writes | Variables, not message rate | | R2 | Cold telemetry history, NDJSON partitioned by project and hour | Total readings over time | | D1 | Users, projects, variable definitions, dashboards, tokens, automations, integrations, audit log | Configuration, not telemetry | | KV | Cached state responses and JWKS | Read traffic | **No telemetry point is ever written to D1.** A reading lands in the project's Durable Object and is flushed to R2 as history. D1 sees only metadata. ## The three ceilings ### 1. Workers requests — this is the one you hit first The free plan allows [**100,000 requests per day**](https://developers.cloudflare.com/workers/platform/limits/), resetting at midnight UTC. Every telemetry POST, every control poll, every dashboard load, and every read-API call is one request. | Devices | Posting every | Requests/day | Free tier | |---|---|---|---| | 1 | 10s | 8,640 | fine | | 5 | 10s | 43,200 | fine | | 11 | 10s | 95,040 | at the edge | | 10 | 60s | 14,400 | fine | | 50 | 60s | 72,000 | fine | | 70 | 60s | 100,800 | over | A WebSocket connection is cheaper than polling here: the connection is one request, and messages on it are not billed as additional requests. A device that polls for control writes every few seconds spends far more of this budget than one that holds a control socket open. ### 2. D1 rows written — driven by variable count, not message rate The free plan allows [**100,000 row writes per day**](https://developers.cloudflare.com/d1/platform/limits/). Because telemetry does not touch D1, what consumes this is mainly the `last_seen` refresh on each variable, and that is throttled to **at most once per minute per variable**. So the rough shape is `variables × 1440` writes per day: | Active variables | D1 writes/day | Free tier | |---|---|---| | 10 | 14,400 | fine | | 30 | 43,200 | fine | | 69 | 99,360 | at the edge | | 100 | 144,000 | over | A project is capped at 250 variables, so a single busy project can exceed the free D1 write allowance on its own. Two caveats keep this an estimate rather than a guarantee: the throttle is held per isolate, so a deployment spread across several isolates can write more often than once per minute per variable; and dashboard edits, automation runs, and audit entries all add writes on top. Row **reads** are unlikely to bind — the free allowance is 5,000,000 per day, and the hot read path is served from the Durable Object and the KV cache rather than D1. ### 3. Storage | | Free | Paid | |---|---|---| | D1 database size | 500 MB | 10 GB | | D1 storage per account | 5 GB | 1 TB | | D1 databases per account | 10 | 50,000 | | D1 queries per Worker invocation | 50 | 1,000 | | Time Travel recovery window | 7 days | 30 days | Because D1 holds metadata only, 500 MB is a great deal of configuration — this is not the limit that ends a hobby deployment. Telemetry history accumulates in R2 instead, which is billed on stored volume rather than capped: [10 GB-month free, then $0.015 per GB-month](https://developers.cloudflare.com/r2/pricing/). ## What happens when you cross a line Since [**1 September 2026**](https://developers.cloudflare.com/changelog/post/2026-09-01-d1-free-tier-limit-enforcement/) Cloudflare enforces D1's free tier limits rather than tolerating overshoot. Past the daily row limit, queries fail outright — on both the Workers binding and the REST API: ``` Your account has exceeded D1's free tier daily row write limit. Upgrade to a paid plan or wait until tomorrow (midnight UTC) to continue. ``` Workers requests behave the same way once the daily allowance is gone. Both reset at midnight UTC, so the failure mode is a deployment that works each morning and stops later in the day — which is worth recognising quickly, because it looks like a bug in your firmware. ## When to move to Workers Paid [Workers Paid](https://developers.cloudflare.com/workers/platform/pricing/) is **$5/month** and includes far more than a hobby deployment consumes: | | Free | Paid (included) | |---|---|---| | Requests | 100,000/day | 10,000,000/month, then $0.30/million | | CPU time | 10 ms/invocation | 30,000,000 CPU-ms/month, then $0.02/million | | D1 rows read | 5,000,000/day | 25,000,000,000/month, then $0.001/million | | D1 rows written | 100,000/day | 50,000,000/month, then $1.00/million | Ten devices posting every ten seconds is about 2.6 million requests a month — a quarter of the included allowance on the paid plan, and comfortably inside the included D1 writes. For most people the honest answer is that the deployment costs nothing until it outgrows the free tier, then five dollars a month flat for a long time after that. ## Reducing what you use - **Hold a control socket instead of polling.** Polling every five seconds costs 17,280 requests per device per day; a WebSocket costs one. - **Post less often.** Most sensors do not change meaningfully every ten seconds. Moving from 10s to 60s cuts request usage sixfold. - **Batch metrics into one request.** A single POST carries many metrics, and it counts once. - **Prune variables you no longer read.** They keep refreshing `last_seen` and keep consuming D1 writes. - **Sleep the device.** Deep sleep between readings saves battery and quota together. ### FAQ **Q: Can nodrix run entirely on Cloudflare's free tier?** For a typical hobby deployment, yes. The ceiling you meet first is the Workers request limit of 100,000 per day, which is roughly eleven devices each posting every ten seconds, or fifty devices each posting every minute. Below that, a free account is genuinely sufficient — there is no trial period and no device cap in nodrix itself. **Q: Does telemetry get written to D1?** No. Telemetry points go to the project's Durable Object, which holds current state and a recent ring buffer, and cold history is flushed to R2 as NDJSON. D1 holds metadata only: users, projects, variable definitions, dashboards, tokens, automations, integrations, and the audit log. No telemetry point is ever written to D1. **Q: What actually consumes my D1 row writes then?** Mostly the last_seen refresh on each variable, which is throttled to at most once per minute per variable. That makes D1 write load a function of how many variables you have, not how often your devices report. Around seventy continuously active variables approaches the free tier's 100,000 daily row writes. **Q: What happens when I exceed a free tier limit?** Since 1 September 2026, Cloudflare enforces D1's free tier limits by failing the query rather than degrading quietly. Workers requests behave the same way once the daily limit is reached. Both reset at midnight UTC. Upgrading to Workers Paid at five dollars a month raises every ceiling well beyond what a hobby deployment reaches. **Q: How much does a real deployment cost per month?** Below the free tier limits, nothing. Above them, Workers Paid starts at five dollars a month and includes ten million requests, thirty million CPU-milliseconds, twenty-five billion D1 rows read, and fifty million D1 rows written. A deployment of a few dozen devices reporting every few seconds sits comfortably inside those included amounts, so the practical answer for most people is five dollars a month flat. --- ## Guide: Does the EU Cyber Resilience Act apply to you? URL: https://nodrix.live/guides/cyber-resilience-act-makers Category: concept Reporting obligations began in September 2026. A decision tree for hobbyists, open-source maintainers, and anyone selling a small batch of boards into the EU. The [Cyber Resilience Act](https://digital-strategy.ec.europa.eu/en/policies/cyber-resilience-act) has generated a great deal of writing, almost all of it produced by compliance vendors for enterprises. If you are one person who publishes firmware, or who sells fifty boards a year, none of it answers your question. This is the decision tree. It is not legal advice, and if you are selling at any scale you should take some — but you should not have to read a law firm's lead-generation piece to work out whether you are in scope at all. ## The dates | Date | What applies | |---|---| | 10 Dec 2024 | Regulation entered into force | | **11 Sep 2026** | [Reporting obligations](https://digital-strategy.ec.europa.eu/en/policies/cra-reporting) live for manufacturers | | 11 Dec 2027 | Full application: essential requirements, CE marking, conformity | | 11 Dec 2027 | Open source stewards' reporting obligations begin | The September 2026 date is narrower than most headlines implied. It brought in **reporting**, not the whole regime, and it applies to manufacturers rather than to everyone. ## Are you in scope? Work down. The first match is your answer. **You publish a project as open source and no money changes hands.** Out of scope. The regulation attaches to economic operators placing products on the EU market, not to individuals sharing code. Contributors to such a project carry no obligations either. Publishing firmware on GitHub does not make you a manufacturer. **You maintain open source that businesses build on, as a legal entity, without monetising it.** You may be an open source steward — a lighter regime than manufacturer, and the reporting duty does not begin until **11 December 2027**. In practice this describes foundations rather than individuals. **You sell hardware into the EU. Any quantity.** You are a manufacturer for those products. The software being open source does not change this, and there is no small-seller exemption. Fifty boards on Tindie counts. **You sell a paid service around the product.** Same answer. Commercial activity is what triggers it, not volume. > **The genuinely unsettled part** > > Where "commercial activity" begins is not precisely defined. Donations, sponsorships and dual > licensing sit in a grey area that the EU has acknowledged rather than resolved. If your project > lives there, that is a real question and not one a guide can close for you. ## If you are not selling into the EU The CRA follows the market, not your address: what matters is whether the product is placed on the EU market. If it is not, this regulation does not reach you — but two others may. **United Kingdom.** The [PSTI regime](https://www.legislation.gov.uk/ukdsi/2023/9780348249767) has been in force since **29 April 2024** and applies to consumer connectable products sold in the UK. It is much shorter than the CRA, with three requirements: passwords must be unique per product or set by the user, you must publish a contact for reporting security vulnerabilities, and you must state the minimum period for which the product will receive security updates. If you sell a connected device to UK consumers, these already apply. **United States.** The FCC's [Cyber Trust Mark](https://www.fcc.gov/CyberTrustMark) is a voluntary labelling programme rather than a mandatory regime, and its rollout has been slow — check its current status before assuming a label is obtainable. There is no federal equivalent of the CRA. **Elsewhere.** Several markets have consumer IoT security baselines in progress or in force. The common core across all of them is the same three things the UK asks for, which is a reasonable standard to build to regardless of where you sell. ## If you are a manufacturer Most maker hardware — a sensor, an actuator, something that reports a reading — sits in the **default category**, which is self-assessed. You do not need a notified body. The stricter classes cover password managers, VPNs, operating systems, firewalls, and smart meters; a temperature sensor is not one of those. Self-assessed still means real obligations by December 2027: - **Secure by default**, and no known exploitable vulnerabilities at the point of sale. - **A documented vulnerability handling process** — how someone reports a problem and how you respond. - **Security updates for the support period**, five years by default unless the expected lifetime is genuinely shorter. - **A machine-readable SBOM** covering the top-level dependencies. - **Technical documentation**, retained for ten years. - **An EU Declaration of Conformity** and CE marking. The one with real engineering consequences is the update obligation. Committing to five years of security updates means committing to a mechanism for delivering them, which is a design decision made long before the first sale. ## What that means in practice Three things follow, and they are worth doing regardless of whether the regulation applies to you: **Ship an update path from the first release.** A board with no way to receive new firmware cannot receive a security fix, and retrieving deployed hardware by hand is not a plan. Building [over-the-air updates](https://nodrix.live/guides/esp32-ota-updates) in from the start costs little; retrofitting them to devices already in the field costs a great deal. **Use per-device credentials, not one shared key.** A single token across a fleet means one extracted device compromises all of them. Espressif's own guidance on physical attacks makes the same point: per-device uniqueness is what stops one compromised board from scaling. This is cheap at design time and impossible to fix later without touching every unit. **Know what is in your firmware.** The SBOM requirement is only painful if you have never tracked your dependencies. Every library you pull in is something you are undertaking to patch. ## A note on where your backend runs One distinction is worth being aware of, and it is genuinely under-discussed: the regulation's scope can extend to remote data processing that the manufacturer provides and the product depends on. A backend you operate on behalf of your customers is a different posture from software your customer deploys into their own infrastructure and runs themselves. nodrix falls on the second side — it deploys into the buyer's own Cloudflare account, and the operator of that deployment is the person who owns it. Whether and how that changes any particular obligation is a question for someone qualified to answer it about your specific product, and the detailed scope here is an area where good public guidance is still thin. It is raised because it is a design decision with regulatory consequences, and those are easier to make early. ## What to do now 1. **Work down the tree.** Most people reading this are in the first branch and have nothing to do. 2. **If you sell into the EU, put a dated note in your repository** covering your support period and how to report a vulnerability. That is the cheapest concrete step and it is genuinely useful to your users. 3. **Make sure you can ship an update.** Everything else is documentation; this one is architecture. 4. **Revisit before December 2027.** Harmonised standards are still being finalised, so the detailed picture will be clearer — and specific — closer to the date. ### FAQ **Q: Does the Cyber Resilience Act apply to hobby projects?** A personal project published as open source, with no money changing hands, is outside the scope. The regulation attaches to economic operators placing products on the EU market, not to individuals sharing code. Publishing firmware on GitHub for free does not make you a manufacturer, and contributors to such a project have no obligations under it. **Q: What changed on 11 September 2026?** Reporting obligations came into force. Manufacturers must report actively exploited vulnerabilities and severe incidents through ENISA's CRA Single Reporting Platform, with an early warning within 24 hours, a full notification within 72 hours, and a final report within 14 days of a fix being available. The wider product requirements follow on 11 December 2027. **Q: When do open source stewards have to report?** From 11 December 2027, not September 2026. This is a distinction a lot of coverage gets wrong. An open source steward is a legal entity providing sustained support to software intended for commercial use without monetising it, which typically means a foundation rather than an individual maintainer. **Q: If I sell a few ESP32 boards on Tindie, am I a manufacturer?** If you are placing them on the EU market, then for those products yes, regardless of quantity or of the software being open source. There is no small-seller exemption. There is support for smaller businesses in the form of guidance, helpdesks and simplified technical documentation, but the obligation categories are the same ones larger companies face. **Q: What class does a typical ESP32 sensor fall into?** The default category, which is self-assessed rather than requiring a third-party body. The stricter classes cover things like password managers, VPNs, operating systems, firewalls and smart meters. A sensor or an actuator that is not performing a security function generally sits in the default class, where you assess conformity yourself and keep the documentation. --- ## Guide: Automate your home against dynamic electricity prices URL: https://nodrix.live/guides/dynamic-tariff-automation Category: project Wholesale-linked tariffs publish a price per interval, sometimes negative. How to pull them in and trigger real actions, in any market that has them. On an Agile-style tariff the electricity price changes every half hour and is published a day ahead. Some periods are several times the price of others. A few are negative, meaning you are paid to use power. Most write-ups about this are affiliate comparisons of tariffs. The technical question — how do you make your house actually respond to the number — has surprisingly little written about it, and the canonical maker post on the subject is years old. ## Where you can do this Dynamic pricing is not available everywhere, and the supplier decides both the interval and how you get the data. The worked example below is **UK Octopus Agile**, because its API is public and needs no key — which makes it the clearest thing to demonstrate. The pattern is identical elsewhere; only the fetch changes. | Region | Supplier / source | Interval | API | |---|---|---|---| | UK | [Octopus Agile](https://docs.octopus.energy/rest/guides/endpoints) | 30 min | REST, no auth | | Nordics, Germany, Netherlands | [Tibber](https://developer.tibber.com/) | 15 min since Oct 2025 | GraphQL, token | | Australia | [Amber Electric](https://app.amber.com.au/developers) | 30 min | REST, token | | Germany, Austria | aWATTar | 60 min | REST | | Nordics, Baltics | Nord Pool day-ahead | 60 min | Licensed | | Central Europe | EPEX SPOT day-ahead | 60 min | Licensed | | US (varies by utility) | e.g. ComEd hourly pricing | 60 min | REST, varies | Two things differ by market and are worth checking before you build: **the interval** — Tibber moved from hourly to quarter-hourly on 1 October 2025, and several markets are hourly rather than half-hourly — and **whether prices are wholesale-linked at all**, since a fixed or time-of-use tariff has nothing to react to. If your market is not listed, the question to ask your supplier is whether they publish forward prices in a machine-readable form. Plenty do not, and no amount of automation helps if the number is only ever on a bill. ## The data is the easy part Octopus publishes half-hourly unit rates through a [REST endpoint](https://docs.octopus.energy/rest/guides/endpoints) that requires **no authentication**: ``` https://api.octopus.energy/v1/products//electricity-tariffs//standard-unit-rates/ ``` Each record carries `value_inc_vat` in pence per kWh, with `valid_from` and `valid_to` marking the half-hour window. The tariff code includes a region letter, so yours differs from the examples you will find online. Results are paginated at 100 records. Tomorrow's prices typically appear in the afternoon, which means a fetch every few hours is plenty — there is no reason to poll aggressively for data that changes once a day. > **The direction of travel** > > [Matter 1.5](https://csa-iot.org/newsroom/matter-1-5-introduces-cameras-closures-and-enhanced-energy-management-capabilities/), > published 20 November 2025, added an electrical energy tariff device type so that "real-time and > forecasted pricing, tariff, and carbon data" can be shared with devices in a standard format. > Price-reactive automation is becoming a smart-home primitive rather than a per-supplier > integration — though certified products in these categories have been slower to arrive than the > specification. ## The pattern Three pieces, and the middle one is what most DIY attempts get wrong: 1. **Fetch** prices on a schedule. 2. **Publish the current price as a variable**, so it is a first-class value that automations can compare against — rather than logic buried inside the fetch script. 3. **Trigger** actions when it crosses a threshold. Keeping the price as a variable is what makes this maintainable. The script's only job becomes "what is the price now", and every decision about what to do with that lives where you can see and change it without editing code. ## Fetching and publishing A small script, run every few hours from cron or systemd, posts the current and next-period prices plus a rank for the day: ```python from datetime import datetime, timedelta, timezone import requests PRODUCT = "AGILE-FLEX-22-11-25" TARIFF = "E-1R-AGILE-FLEX-22-11-25-C" # region letter differs — check yours RATES = (f"https://api.octopus.energy/v1/products/{PRODUCT}" f"/electricity-tariffs/{TARIFF}/standard-unit-rates/") NODRIX = "https://nodrix.you.workers.dev/v1/telemetry" TOKEN = "tok_your_project_token" now = datetime.now(timezone.utc) window = { "period_from": now.isoformat().replace("+00:00", "Z"), "period_to": (now + timedelta(hours=24)).isoformat().replace("+00:00", "Z"), } results = requests.get(RATES, params=window, timeout=15).json()["results"] periods = sorted(results, key=lambda r: r["valid_from"]) if not periods: raise SystemExit("no prices published for this window") current = periods[0] prices = [p["value_inc_vat"] for p in periods] # Rank 0 means this is the cheapest half hour in the next 24. rank = sorted(prices).index(current["value_inc_vat"]) requests.post( NODRIX, headers={"Authorization": f"Bearer {TOKEN}"}, json={"metrics": { "price_now": current["value_inc_vat"], "price_next": periods[1]["value_inc_vat"] if len(periods) > 1 else None, "price_rank": rank, "price_min_24h": min(prices), "price_max_24h": max(prices), }}, timeout=10, ) ``` `price_rank` is the variable that makes this genuinely useful. An absolute threshold like "below 10p" needs revisiting whenever the market moves. "This is one of the four cheapest half hours in the next twenty-four" keeps meaning the same thing regardless of where prices sit, which matters because you will otherwise be editing thresholds every few months. ## Turning price into action With the price arriving as a variable, the decisions live in automations on your deployment rather than in the script: - **Immersion heater when `price_rank` is in the lowest four.** Heating water at 03:00 is identical to heating it at 18:00 except for the cost, which makes it the ideal load to shift. - **EV charging when `price_now` is below your own average.** The car does not care when it charges, only that it is full by morning. - **A notification when `price_now` goes negative**, so you can run the dryer while being paid to. - **Storage battery charge on the cheapest periods**, discharge on the most expensive, if you have one. - **Stop discretionary loads when `price_now` exceeds `price_max_24h` minus a margin** — the peak periods where the cost of running something flexible is worst. Each is a trigger, a condition and an action, and the action can be a control write to a relay, a webhook, or a message through a chat integration. The switching side is the same as any other load control; the [smart home automation guide](https://nodrix.live/guides/esp32-smart-home-automation) covers doing it safely, and mains switching deserves that care. On the dashboard, charting `price_now` against `price_min_24h` and `price_max_24h` shows the shape of the day at a glance, and it is the quickest way to sanity-check that a rule is firing at the times you intended rather than at 4 a.m. for reasons you have not noticed yet. ## What is worth automating The honest ranking, by how much the shift is worth against how much effort it takes: | Load | Shiftable | Worth it | |---|---|---| | Immersion heater | Completely | Yes — large load, timing irrelevant | | EV charging | Completely | Yes — largest single load in most homes | | Storage battery | Completely | Yes, if you have one | | Dehumidifier, pool pump | Mostly | Reasonable | | Dishwasher, washing machine | Partly | Marginal — small loads, needs a smart appliance | | Fridge, freezer | No | No — and do not try | The pattern rewards big flexible loads. Automating a dishwasher to save a few pence is a fun afternoon and not much else; shifting an immersion heater or a car charger is where the numbers stop being rounding errors. ### FAQ **Q: Can I automate my home against Octopus Agile prices?** Yes, and the price data is the easy part. Octopus publishes half-hourly unit rates through a REST endpoint that needs no authentication at all, so any script or device can read tomorrow's prices as soon as they are released. The work is turning those prices into a trigger something can act on. **Q: Do I need Home Assistant for this?** No. Home Assistant has good integrations for it and if you already run one, use it. What the pattern actually requires is something that fetches prices on a schedule, a value your automations can compare against, and something that can switch a load. None of that is specific to any one platform. **Q: What is the Matter tariff device type?** Matter 1.5, published in November 2025, added a device type for sharing real-time and forecast electricity pricing, alongside earlier support for solar, batteries, heat pumps and EV charging. It means price-reactive automation is becoming a standard smart-home primitive rather than something each vendor invents. Certified products in these categories have been slower to arrive than the specification itself. **Q: What can I actually control on price?** Anything where timing is flexible and the load is meaningful: immersion heaters, EV charging, storage batteries, dehumidifiers, pool pumps, and to a lesser extent dishwashers and washing machines. The test is whether the job cares when it happens. Heating a tank of water at 03:00 is identical to heating it at 18:00, except for the price. **Q: Are negative prices real?** They happen, usually overnight or on windy weekends when generation exceeds demand. During those periods you are paid to consume. That is the headline case, but it is not where most of the value is: the routine gap between the cheapest and most expensive half hours on an ordinary day is the thing that adds up. --- ## Guide: ESP32 certificate verification failed: pinning and rotation URL: https://nodrix.live/guides/esp32-certificate-verification-failed Category: concept · Board: ESP32 What the TLS handshake error actually means, why setInsecure is not a fix, and how to pin a certificate so a CA rotation does not take your whole fleet offline. The board connects to Wi-Fi, the request goes out, and the handshake fails: ``` [E][ssl_client.cpp] start_ssl_client(): (-9984) X509 - Certificate verification failed ``` The first search result tells you to call `setInsecure()`. That makes the error disappear, which is not the same as fixing it. ## What the error means The handshake got far enough to receive the server's certificate chain, and the board decided not to trust it. Three causes account for nearly all of it: - **No trusted root loaded.** Your laptop carries a trust store maintained by its operating system. An ESP32 has nothing unless you give it something. - **A pin that no longer matches.** You pinned a certificate or fingerprint and the server has since been issued a new one. - **The clock is wrong.** Certificates are only valid between two dates. A board that has just powered on may think it is January 1970, which is outside every validity window ever issued. The third catches people out because it is intermittent by nature: it fails on cold boot and works after time is set. ## Why setInsecure is not the answer `setInsecure()` turns verification off. The handshake then succeeds against any certificate at all, including one presented by something sitting between you and your server. The connection remains encrypted, which is exactly why it looks like it works. You simply no longer know who is on the other end — and on a device that can act on the physical world, "encrypted to someone unspecified" is a meaningfully worse position than it sounds. It is a useful five-minute debugging step to confirm the problem is verification and not routing. It is not a setting to ship. ## Pin the root, not the leaf There are two levels you can pin at, and the choice decides how much maintenance you have signed up for. | | Fingerprint / leaf | Root CA | |---|---|---| | Survives certificate renewal | No | Yes | | Survives CA rotation | No | No | | Typical lifetime | Weeks to months | Years | | RAM cost | Lower | Higher | | Breaks when | The server renews | The CA rotates | A leaf certificate is replaced routinely — often every couple of months with modern automated issuance. Pinning it means every one of those renewals takes your fleet offline until you reflash. Pinning the root survives renewals, because the new leaf still chains to the same root. It costs more RAM, which is why fingerprint pinning exists at all: on an ESP8266 with roughly 40 KB of usable heap, full chain validation is a genuine squeeze. On an ESP32 it is a non-event, and the maintenance difference compounds across a fleet's lifetime. ## Setting it up The nodrix library exposes all three options, and is explicit that **TLS is unvalidated until you pin one of them**. Pick before `begin()`: ```cpp #include #if defined(ESP8266) #include #else #include #endif #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; // ESP8266 only: SHA-1 fingerprint of the server's leaf certificate. const char* HOST_FP = "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD"; // Paste your deployment's root CA here, including both marker lines. static const char ROOT_CA_PEM[] PROGMEM = R"( -----BEGIN CERTIFICATE----- MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh ...paste the rest of your root certificate here... -----END CERTIFICATE----- )"; void setup() { Serial.begin(115200); // NTP needs the network, so Wi-Fi comes up first here rather than letting // Nodrix.begin() do it. That is what the two-argument begin() is for. WiFi.begin(WIFI_SSID, WIFI_PASS); while (WiFi.status() != WL_CONNECTED) delay(200); // Certificates are time-bounded, so the clock has to be right before the // first handshake. Without this, a cold boot fails verification every time. configTime(0, 0, "pool.ntp.org", "time.nist.gov"); while (time(nullptr) < 8 * 3600 * 2) delay(200); #if defined(ESP8266) Nodrix.setFingerprint(HOST_FP); // chain validation is heavy for 40 KB of heap #else Nodrix.setCACert(ROOT_CA_PEM); // ESP32: pin the root, survive renewals #endif Nodrix.setFirmwareVersion("1.0.0"); Nodrix.begin(HOST, TOKEN); // Wi-Fi is already up } void loop() { Nodrix.run(); static unsigned long last = 0; if (millis() - last >= 30000) { last = millis(); Nodrix.send("uptime_s", (long)(millis() / 1000)); Nodrix.send("rssi", WiFi.RSSI()); } } ``` The `configTime` block is the part most write-ups omit, and it is the cause of the "works sometimes" version of this error. The wait loop blocks until the clock is plausibly correct rather than assuming NTP has completed. > **On ESP-IDF rather than Arduino** > > ESP-IDF ships an [x509 certificate bundle](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/protocols/esp_crt_bundle.html) > you attach with `esp_crt_bundle_attach`, carrying the Mozilla NSS root store rather than a single > PEM you embed yourself. If you are on IDF, prefer it — it removes the > single-root fragility described below. ## The failure mode worth designing against A root CA rotation takes down every pinned device simultaneously. Because those devices connect over TLS, and TLS is what just broke, you cannot reach them to fix it. This is the scenario that turns a certificate detail into a fleet incident, and it has a shape: 1. The CA announces a rotation, usually months ahead. 2. Your devices are pinned to the outgoing root. 3. On the cutover date, every one of them stops connecting. 4. Each needs physically reflashing, because the remote update path is also TLS. The defence is to change the pin **before** the old root stops being served, while the devices can still reach you. That means firmware updates are not a nice-to-have on a pinned fleet — they are the recovery mechanism, and they have to work before you need them. Practically: - **Ship OTA from the first release**, even when the first firmware is otherwise finished. A board without an update path is a board you will eventually retrieve by hand. - **Report the running version** as a variable, so you can see which devices have taken a new root and which have not. A rollout you cannot observe is a rollout you cannot trust. - **Watch the heartbeat during a rotation.** Devices dropping off in a cluster is the signal that the new pin did not reach everything. On nodrix, the running version is reported back and shown per device, so a pin change is visible as it propagates rather than discovered afterwards. The [OTA guide](https://nodrix.live/guides/esp32-ota-updates) covers the rollout mechanics. ## Quick triage | Symptom | Likely cause | |---|---| | Fails always, on every board | No root loaded, or wrong root for the chain | | Fails on cold boot, works later | Clock not set before the first handshake | | Worked for months, now fails everywhere at once | Certificate renewed and you pinned the leaf | | Works on one board, fails on another | Different firmware, different pin | | Works with `setInsecure()` | Confirms verification is the issue — now fix it properly | ### FAQ **Q: What does 'X509 - Certificate verification failed' mean on an ESP32?** The TLS handshake completed far enough to receive the server's certificate chain, and the board rejected it. The usual causes are no trusted root loaded, a pinned certificate that has since been replaced, or a clock that is wrong enough to put the certificate outside its validity window. The last one is common on a board that has just powered on and has not yet set its time. **Q: Is setInsecure a valid fix?** No. It disables verification, so the handshake succeeds with any certificate at all, including one presented by whatever is between you and the server. The connection is still encrypted, which is why it looks like it works, but you have no idea who you are encrypting to. It is a debugging step, not a configuration. **Q: Should I pin the leaf certificate or the root CA?** The root. A leaf certificate is replaced every few months, and pinning it means every renewal breaks every device until you reflash them. A root CA typically lasts years, so pinning it survives ordinary certificate renewal. Fingerprint pinning is the leaf-level version of this and carries the same maintenance cost. **Q: What happens when the root CA rotates?** Every device pinned to the old root stops connecting, all at once, and because they connect over TLS they cannot be reached to be fixed. This is the failure mode worth designing against: root rotations are announced well in advance, so the recovery path is to push firmware carrying the new root before the old one stops being served. **Q: Why does TLS work on my laptop but fail on the ESP32?** Your laptop carries a large trust store maintained by its operating system, and its clock is correct. An ESP32 has neither unless you provide them. That is the entire difference in most cases: no trusted roots loaded, or a clock sitting at January 1970 which places every certificate outside its validity window. --- ## Guide: ESP32 keeps restarting: reading the reset reason URL: https://nodrix.live/guides/esp32-keeps-restarting Category: concept · Board: ESP32 Brownout, task watchdog, Guru Meditation and bootloop produce different serial output. Read the string, find the cause, and see reboots on remote boards. A board that restarts on its own is not one problem, it is four, and they are easy to tell apart because each prints something different at the moment it happens. Open the serial monitor at 115200 and read the line printed immediately after the restart. Everything follows from that string. ## The four signatures | Serial output | Cause | Where to look | |---|---|---| | `Brownout detector was triggered` | Supply voltage sagged | Power, cable, regulator, capacitors | | `Task watchdog got triggered` | A task blocked too long | The named task's loop | | `Guru Meditation Error … LoadProhibited` | Code faulted | Null or uninitialised pointer | | `rst:0x10 (RTCWDT_RTC_RESET)` repeating | Fails before your code runs | Flash, partition table, bad image | ### Brownout This is power, not code, almost every time. The chip detected the supply falling below threshold and reset deliberately rather than behaving unpredictably. It shows up the moment the radio transmits, because a Wi-Fi burst can draw several hundred milliamps in spikes against an idle draw of a few tens. Anything sized for the average will sag. The usual culprits, in the order worth checking: - A thin or long USB cable. Many are charge-oriented and drop meaningful voltage under load. - A laptop port or hub that cannot supply the peak. - Powering from a sensor board's 3.3 V regulator rather than a supply sized for the module. - No bulk capacitance near the module. A few hundred microfarads across the supply pins absorbs the spikes that the regulator cannot respond to quickly enough. If it only browns out on battery, the battery's internal resistance is the constraint, not capacity. ### Task watchdog A task held a core longer than the timeout without yielding. The message names the task, which is usually enough. Typical causes are a long blocking loop with no yield, `delay()` used inside a task where `vTaskDelay()` belongs, a slow flash or filesystem write, or a network call without a timeout. The fix is to yield, or to move the work off the loop, rather than to raise the timeout — raising it hides the symptom and the underlying stall remains. ### Guru Meditation The code faulted. `LoadProhibited` is the most common and means a null or uninitialised pointer was dereferenced. The backtrace is a list of addresses that mean nothing by themselves. Decode it with the ESP Exception Decoder against the **exact ELF from the build that crashed** — a rebuilt binary produces different addresses and a plausible but wrong answer, which is worse than none. ### Bootloop before your code If the reset repeats with `rst:0x10 (RTCWDT_RTC_RESET)` and you never see your own output, it is failing before `setup()`. Usually a bad flash, a partition table that does not match the image, or an image built for a different chip. Erase the flash entirely and reflash rather than flashing over the top. ## Seeing it on a board you cannot reach The serial monitor works when the board is on your desk. It is no use when the device is in a greenhouse and restarting once a day. The [ESP-IDF system API](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/misc_system_api.html) exposes why the last reset happened, so a board can report its own cause on the way back up: ```cpp #include #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; const char* resetReason() { switch (esp_reset_reason()) { case ESP_RST_POWERON: return "poweron"; case ESP_RST_SW: return "software"; case ESP_RST_PANIC: return "panic"; // Guru Meditation case ESP_RST_INT_WDT: return "int_wdt"; case ESP_RST_TASK_WDT: return "task_wdt"; case ESP_RST_BROWNOUT: return "brownout"; case ESP_RST_DEEPSLEEP:return "deepsleep"; default: return "other"; } } void setup() { Serial.begin(115200); Nodrix.setFirmwareVersion("1.0.0"); Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); // Reported once per boot: why we restarted, and an event to mark the moment. Nodrix.send("reset_reason", resetReason()); Nodrix.event("device_boot"); } void loop() { Nodrix.run(); static uint32_t last = 0; if (millis() - last > 60000) { last = millis(); Nodrix.send("uptime_s", (long)(millis() / 1000)); Nodrix.send("heap_free", (long)ESP.getFreeHeap()); } } ``` Three variables turn a mystery into a diagnosis: - **`reset_reason`** distinguishes the four cases above without a cable. `brownout` points at power, `panic` at code, `task_wdt` at a blocking loop. - **`uptime_s`** resets to zero on every restart. A sawtooth on the chart is a board rebooting, and the period tells you how often — a detail that is invisible if you only look at sensor values. - **`heap_free`** trending downward over hours is a memory leak, and the crash that eventually follows is a consequence rather than the problem. This is the one you cannot see any other way. On the deployment, an automation on the `device_boot` event sends to Telegram, Discord, Slack or email, so an unexpected restart arrives as a message rather than as a gap you notice next week. A board that reboots nightly at 03:00 tells you something quite specific about your power situation, but only if someone is counting. ## Order of attack 1. **Read the serial line.** It identifies which of the four you have and saves hours of guessing. 2. **Rule out power first.** It is the cheapest to test — a better cable and a proper supply — and it is the most common cause on a board that worked on the bench and fails in place. 3. **Decode before theorising.** A backtrace with the right ELF is precise; a guess about it is not. 4. **Watch `heap_free` if it takes hours to fail.** Slow failures are usually leaks, and no amount of staring at the crash point reveals that. ### FAQ **Q: Why does my ESP32 keep restarting?** The board prints the reason on the serial monitor at the moment it restarts, and the four common causes look completely different. Brownout means the supply sagged. A task watchdog trigger means something blocked for too long without yielding. A Guru Meditation Error means the code faulted, usually on a null or out-of-bounds access. A bootloop with an RTCWDT reset means it is failing before your code runs at all. **Q: What does 'Brownout detector was triggered' mean?** The supply voltage dropped below the threshold, so the chip reset itself rather than behaving unpredictably. It is almost always power rather than code: a USB port or cable that cannot deliver the current a Wi-Fi transmit burst demands, a regulator sized for idle draw instead of peak, or missing bulk capacitance near the module. It typically appears the moment the radio transmits. **Q: What causes 'Task watchdog got triggered'?** A task held a core for longer than the watchdog timeout without yielding. Common causes are a long blocking loop, a delay inside a task that should be using vTaskDelay, a slow flash or filesystem write, or a network call with no timeout. The message names the task that was blocked, which is usually enough to find it. **Q: How do I decode a Guru Meditation backtrace?** The backtrace is a list of addresses that mean nothing on their own. Feed it to the ESP Exception Decoder with the exact ELF file from the build that produced the crash, and it maps to file and line numbers. LoadProhibited almost always means dereferencing a null or uninitialised pointer. **Q: How do I tell why a remote board rebooted without a serial cable?** Report the reset reason and the uptime as variables when the board starts up. Then a restart is visible on the dashboard as a reason you can read and an uptime that drops to zero, rather than a gap you have to interpret. Without that, a crashing board and a flaky network look identical from the outside. --- ## Guide: Access an ESP32 from anywhere without port forwarding URL: https://nodrix.live/guides/esp32-remote-access-no-port-forwarding Category: concept Four ways to reach a board on your home network from outside it, what each costs in setup and risk, and why outbound-only suits most projects. You have an ESP32 on your home Wi-Fi reporting something you care about, and you want to see it from work. Every answer you find says "forward port 80 on your router", which is both the oldest advice and the worst. There are four real options. Only one of them requires nothing from your router. ## The four approaches | Approach | Router config | Exposed to the internet | Sharing with others | |---|---|---|---| | Port forwarding | Port forward + dynamic DNS | The board itself | Anyone with the URL | | VPN (Tailscale, WireGuard) | Usually none | Nothing | Only tailnet members | | Tunnel client | None | The tunnel endpoint | Anyone with the URL | | Outbound to your own backend | None | Nothing | However you choose | ### Port forwarding You open a port on your router and point it at the board's local IP, then add dynamic DNS because your home IP changes. This publishes a microcontroller to the open internet. An ESP32 running a small HTTP server has a minimal TLS stack, no rate limiting worth the name, and whatever patch cadence you personally maintain. Mass scanners will find it within hours of it going up — not because anyone targeted you, but because the entire address space is swept continuously. If you do this anyway: put it on a separate VLAN, never expose a board that can switch something physical, and be honest with yourself about whether you will apply updates. ### VPN [Tailscale](https://tailscale.com/) or [WireGuard](https://www.wireguard.com/) put your phone and your device on the same private network. Nothing is exposed, and for a Raspberry Pi or a home server this is genuinely the right answer. On a microcontroller it fits less well. You are adding a daemon and key management to a device whose job is to report a temperature, and everyone who wants to see the data has to join the tailnet. If you want to show a dashboard to someone in the house who is not technical, that is a real obstacle. ### Tunnel client A tunnel client on the board, or on a Pi next to it, dials out to a provider — [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) is the common one — which gives you a public URL. No router changes, and the inbound path terminates at the provider rather than on your device. The trade is that you have added a dependency, usually a daemon that wants more resources than a microcontroller has, which is why this is typically run on a Pi rather than on the ESP32 itself. ### Outbound to a backend you control The board opens the connection. It makes an HTTPS request, or holds a WebSocket, to a backend — the same thing a browser does when it loads a page. Your router permits that already. Nothing listens on your home IP. There is no port to forward, no dynamic DNS, no VPN membership, and nothing for a scanner to find, because from the network's point of view your board is a client, not a server. The catch, and it is the real one: you need a backend for it to dial. If that backend is somebody's IoT cloud, you have swapped a router problem for a vendor problem — device caps, message quotas, and your data on their infrastructure. ## Running the backend yourself nodrix is that backend, deployed to your own Cloudflare account. The board dials out to it, the data lands in your tenancy, and there is no per-device pricing. The device side is small on purpose: ```cpp #include #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; const int RELAY_PIN = 2; // LED_BUILTIN on most dev boards, so this runs unwired void setup() { Serial.begin(115200); pinMode(RELAY_PIN, OUTPUT); Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } // A dashboard control writing back down the same outbound connection. NODRIX_WRITE(relay) { digitalWrite(RELAY_PIN, value.asBool()); } void loop() { Nodrix.run(); static unsigned long last = 0; if (millis() - last >= 30000) { last = millis(); Nodrix.send("uptime_s", (long)(millis() / 1000)); Nodrix.send("rssi", WiFi.RSSI()); } } ``` That compiles and runs on a bare board with nothing wired to it — `uptime_s` and `rssi` need no sensor, and `RELAY_PIN` is the built-in LED on most dev boards, so the dashboard toggle is visible immediately. Swap in your own sensor and pin once the connection is proven. Note what is not there: no server, no port, no DDNS hostname, no VPN config. **Control works the same way**, which is the part people expect to need an inbound connection for. The board holds a WebSocket out to your deployment; when you move a toggle on the dashboard, the write travels down the connection the board already opened. A sleeping device polls for pending writes when it wakes instead. Either way nothing connects inward. From there the useful parts are on the deployment rather than the device: - **Dashboards** that stream over a WebSocket, shareable read-only by link — which is how you show someone the data without giving them access to anything. - **Automations** on variable, schedule and sunrise/sunset triggers, so alerts fire without the board needing to be reachable. - **A read API** for Grafana or your own app, behind one token. ## Which to pick - **A sensor reporting somewhere, or a relay you want to flip from your phone.** Outbound. There is no router configuration and no exposure, and it is the only option that stays simple when you add a second device. - **You already run Tailscale and want to reach a Pi.** Use it. Adding a second mechanism for one board is not worth it. - **You need to serve a real web app from the board itself.** A tunnel, on a Pi beside it. - **Port forwarding.** Only behind a VLAN, only for something that cannot act physically, and only if you will keep it patched. The reason outbound wins for most projects is not that it is clever. It is that it removes the question entirely: there is nothing to configure on the router, so there is nothing to get wrong. ### FAQ **Q: How do I access my ESP32 from outside my home network?** There are four workable approaches: forward a port on your router, put the board and your phone on a VPN such as Tailscale or WireGuard, run a tunnel client that dials out to a tunnel provider, or have the board make an outbound connection to a backend you control and talk to that instead. The last one needs no router configuration and no inbound path, which is why it is the usual answer for a sensor or a relay. **Q: Is port forwarding to an ESP32 safe?** It is the riskiest of the four. Forwarding a port publishes a device with a small TLS stack, a simple HTTP server and no meaningful update cadence directly to the internet, where it will be scanned within hours. If you do it anyway, put the board on a separate VLAN, never expose a device that can act on the physical world, and be certain you can patch it. **Q: Do I need a static IP or dynamic DNS?** Only if something outside your network needs to open a connection inward, which is the case for port forwarding and for hosting a server on the board. If the board dials out instead, your home IP can change as often as your ISP likes and nothing notices. **Q: Is Tailscale a good option for an ESP32?** Tailscale is excellent for reaching a Raspberry Pi or a home server, and if you already run it, using it is reasonable. It is a heavier fit for a microcontroller: you are adding a daemon and key management to a device whose whole job is to report a number, and every client that wants the data has to be on the same tailnet. For a phone dashboard you want to share, that is friction. **Q: What does outbound-only actually mean?** The board opens the connection, not the internet. It makes an HTTPS request or holds a WebSocket to a backend, exactly as a browser does when you load a page. Your router already allows that, so there is nothing to configure, nothing listening on your home IP, and nothing for a scanner to find. --- ## Guide: ESP32 Wi-Fi keeps disconnecting: a reconnect that holds URL: https://nodrix.live/guides/esp32-wifi-keeps-disconnecting Category: concept · Board: ESP32 Why a board that connects fine drops out after hours, the reconnect loop that actually survives it, and how to know a device went offline instead of guessing. The board connects fine on the bench. Then it runs for eleven hours and stops, and the next morning you power-cycle it and it runs for another eleven. The usual advice is to call `WiFi.reconnect()` in a loop, which is where most write-ups end and where the real problem starts. ## Why it actually drops A long-running disconnect is nearly always the network doing something, not the board failing: - **The association expires.** Access points periodically require a fresh handshake. A client that assumes its authentication is permanent gets dropped and does not always recover on its own. - **DHCP lease renewal fails.** The lease expires, renewal does not complete, and the board holds an address it no longer owns. - **Band steering.** A router publishing one SSID across 2.4 and 5 GHz decides the client belongs on 5 GHz. Most ESP32 variants cannot join it, so the SSID is visibly present and unjoinable. The [ESP32-C5](https://www.espressif.com/en/news/ESP32-C5_Mass_Production) is the exception — it is the first dual-band part in the family. - **Mesh roaming.** A multi-node system moves the client to a different node. Well-behaved clients follow; a board that cached one BSSID may not. - **Edge of range.** Reported signal looks acceptable but sits close to the threshold, so ordinary interference tips it over. Only the last is about the board's radio. The rest are events it is correctly reporting, which is why retrying harder does not help. ## What a reconnect needs Four things, in order of how much they matter: 1. **Backoff.** Retry after a second, then two, then four, capped around a minute. A tight retry loop recovers no faster and on some routers looks enough like abuse to get you rejected. 2. **A reboot fallback.** After a number of failed attempts, restart. This clears driver and TCP state a reconnect cannot reach, and it is what separates a device that recovers by itself from one you have to go and unplug. 3. **More than one network.** If a second access point is reachable, having the board choose the strongest removes band steering and roaming as single points of failure. 4. **Knowing it happened.** A board that is off the network cannot report that it is off the network. That has to come from the other end. ## The device side The [nodrix Arduino library](https://github.com/decoded-cipher/nodrix-sdk) handles the loop, the backoff and the reconnect, so the sketch stays about the work rather than about the network. What it adds is the fourth item — a heartbeat, so the absence of a device is itself a signal: ```cpp #include #include const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; const unsigned long HEARTBEAT_MS = 60000; void setup() { Serial.begin(115200); // Register every network the board might see; the strongest reachable one wins. Nodrix.addAP("house-2.4", "your-password"); Nodrix.addAP("garage", "your-other-password"); Nodrix.setFirmwareVersion("1.0.0"); Nodrix.onConnect([] { Serial.println("link up"); }); Nodrix.onDisconnect([] { Serial.println("link lost"); }); Nodrix.begin(HOST, TOKEN); // networks come from addAP above } void loop() { Nodrix.run(); // reconnect and backoff live here static unsigned long lastBeat = 0; if (millis() - lastBeat >= HEARTBEAT_MS) { lastBeat = millis(); Nodrix.send("uptime_s", (long)(millis() / 1000)); Nodrix.send("rssi", WiFi.RSSI()); } } ``` Two variables carry most of the diagnostic value: - **`uptime_s`** resets to zero on reboot. A sawtooth on that chart is a board restarting, and the period tells you how often. It is the difference between "the network is flaky" and "the board is crashing", which look identical from the dashboard otherwise. - **`rssi`** logged over time shows whether disconnects line up with signal dropping. If they do, the fix is an antenna or a location, not code. ## Knowing it went offline This is the part a reconnect loop cannot do, and it is why the interesting half of this problem lives on the backend rather than on the board. Because the heartbeat arrives on an interval, its absence is detectable. On your deployment: - Add a **last-seen** widget to the dashboard, so a stale device is visible at a glance rather than hiding behind a plausible-looking last reading. - Build an **automation** that fires when the heartbeat stops arriving, and have it send to Telegram, Discord, Slack or email through an integration. - Chart **`uptime_s`** and **`rssi`** together. The shape of those two lines usually identifies the cause without a serial cable. That distinction — offline versus unchanged — is the one you cannot make by looking at the last value. A temperature that reads 21.4 °C is indistinguishable from a board that died three hours ago while reporting 21.4 °C, unless something is watching for the silence. ## Router-side fixes worth trying Before rewriting more firmware, some of these are a five-minute change and remove the cause outright: | Symptom | Try | |---|---| | SSID visible, board will not join | Split the 2.4 GHz SSID from 5 GHz, or disable band steering for it | | Drops when moving between rooms | Pin the board to one access point, or accept roaming and rely on reconnect | | Drops at a consistent interval | Look at the DHCP lease time; a static reservation removes renewal as a cause | | Drops only under load | Check for channel congestion; 2.4 GHz is crowded and the ESP32 has a small antenna | A static DHCP reservation on the router is worth doing regardless. It makes the board's address stable without hardcoding a static IP in firmware, which is the version of this that breaks when you move house. ## What good looks like A board that has been up for weeks, a heartbeat that has not gaped, and an alert that would have told you if it had. If `uptime_s` shows a sawtooth you have a crash rather than a network problem, and that is a different guide — but at least you now know which one you are reading. ### FAQ **Q: Why does my ESP32 keep disconnecting from WiFi?** Most long-running disconnects are not the board failing. The common causes are the access point expiring the association and requiring a fresh handshake, DHCP lease renewal going wrong, band steering moving the SSID to a 5 GHz radio the ESP32 cannot join, a mesh system roaming the client to a different node, and weak signal at the edge of range. The board is usually reporting a real network event, which is why a blind retry loop rarely fixes it. **Q: Why does my ESP32 reconnect but then drop again immediately?** Usually because the retry has no backoff. A tight loop hammering the access point on failure often makes things worse, and on some routers it looks enough like abuse to get the client temporarily rejected. Backing off progressively, from about a second up to roughly a minute, both recovers faster in practice and stops the board making its own situation worse. **Q: Should I use WiFi.setAutoReconnect?** It is worth enabling, but do not rely on it alone. It handles the simple case where the association drops and the same credentials still work. It does not help when the authentication state has expired, when DHCP fails, or when the access point has moved to a band the board cannot use, and those are exactly the cases that produce the slow overnight failures people complain about. **Q: How do I know my ESP32 went offline?** From the other end, because a board that is off the network cannot tell you anything. Send a heartbeat on a regular interval and have the backend alert when it stops arriving. That distinguishes a device that is offline from a sensor reading that simply has not changed, which is a distinction you cannot make by looking at the last value. **Q: Should the board reboot itself if WiFi will not come back?** As a last resort, yes. After a number of failed attempts, a restart clears driver and TCP state that a reconnect loop cannot reach, and it is the difference between a device that recovers by itself and one you have to go and power-cycle. Make it the fallback after backoff has genuinely failed, not the first response to a dropped packet. --- ## Guide: ESPHome without Home Assistant: reaching it remotely URL: https://nodrix.live/guides/esphome-without-home-assistant Category: concept ESPHome is local-first by design. The honest options for seeing your devices from outside the house, including pushing readings out without leaving ESPHome. ESPHome is firmware, and a good one — a clean configuration model, a large library of supported sensors, and local control that keeps working when your internet does not. Paired with Home Assistant inside the house it is genuinely hard to beat. It is also local-first on purpose, which is why "how do I see this from work" is one of the most repeated questions in the community and one of the least well answered. The threads are plentiful; the write-ups are not. ## What ESPHome gives you on its own A device runs standalone. With the [web server component](https://esphome.io/components/web_server.html) enabled you can open its IP on your network and see current values and controls. No Home Assistant required. What you do not get is the layer above: history beyond what is on screen, dashboards spanning several devices, automations across boards, or alerting. ESPHome does not claim to provide those — it is firmware, and that is the boundary. ## The four remote options | Approach | Setup | Works when internet is down | Sharing with non-technical people | |---|---|---|---| | Home Assistant + Nabu Casa | Subscription | Locally, yes | Good | | VPN (Tailscale, WireGuard) | Per-client config | Locally, yes | Poor — everyone needs the VPN | | Tunnel to a dashboard | Moderate | Locally, yes | Good | | Push readings outward | Small | Local control unaffected | Good | The first three make your network reachable from outside. The fourth reverses the direction: the device sends readings out, so nothing inbound is required at all. That last one is worth understanding properly, because it composes with the others rather than replacing them. ## Keeping ESPHome and adding remote The honest configuration for most people is not a migration. It is ESPHome continuing to do local control exactly as it does now, plus an outbound POST so the readings are also somewhere you can reach. ESPHome's [`http_request` component](https://esphome.io/components/http_request.html) makes HTTPS POSTs with a JSON body and custom headers. By default it validates certificates against ESP-IDF's bundled CA set, so this is a properly verified connection rather than one with checks disabled. Pointing it at a nodrix deployment is one block of YAML: ```yaml # Local control is unchanged — this is added alongside it. api: encryption: key: !secret api_key http_request: verify_ssl: true # uses ESP-IDF's bundled CA set timeout: 10s sensor: - platform: bme280_i2c temperature: name: "Greenhouse temperature" id: temp humidity: name: "Greenhouse humidity" id: humidity address: 0x76 update_interval: 60s # Push a copy outward every five minutes. interval: - interval: 5min then: - http_request.post: url: https://nodrix.you.workers.dev/v1/telemetry request_headers: Content-Type: application/json Authorization: !secret nodrix_token json: metrics: temperature: !lambda return id(temp).state; humidity: !lambda return id(humidity).state; on_error: then: - logger.log: "telemetry post failed" ``` Points worth noting: - **The `api:` block is untouched.** Home Assistant keeps its native connection, local automations keep running, and none of that depends on the POST succeeding. - **Variables appear on first sight.** There is no schema to define on the receiving end — send `temperature` and a temperature variable exists. - **Five minutes, not sixty seconds.** The local sensor updates every minute for local automations; the outbound copy is for remote viewing and alerting, where a five-minute resolution is usually plenty and uses a fraction of the request budget. - **`on_error` logs rather than retries.** A failed POST should not disturb a device whose primary job is local control. ## What that gets you The readings now exist somewhere reachable without a VPN, a tunnel, or an inbound port, in a deployment in your own Cloudflare account: - **Dashboards** that stream over a WebSocket and can be shared read-only by link — which is how you show someone the greenhouse without giving them access to your home network. - **Automations** on variable, schedule and sunrise/sunset triggers, sending to Telegram, Discord, Slack or email. Useful for the alerts you want to arrive whether or not you are home. - **A read API** behind one token, for Grafana or an app of your own. - **History** beyond what the device holds in memory. Local control stays where it belongs. If your internet drops, ESPHome and Home Assistant carry on exactly as before and the outbound posts simply resume when the connection returns. ## Choosing - **Everything is inside the house and stays there.** ESPHome plus Home Assistant. Nothing here improves on that. - **You want remote access to the whole Home Assistant instance.** Nabu Casa, or Tailscale if you prefer to run it yourself. Both are good answers. - **You want specific readings visible and alerting reliably from anywhere, including to people who will not install a VPN.** Push them outward. It composes with either of the above. - **You are starting fresh, remote-first, and have no Home Assistant.** Then the question is different — a device reporting outbound from the start does not need the local platform at all, and [connecting an ESP32 over HTTPS](https://nodrix.live/guides/esp32-https-cloud) is the shorter path. The framing that causes trouble is treating this as a choice between platforms. ESPHome is firmware and a backend is a backend; the reason the forum threads go in circles is that people keep looking for one thing that does both, when running both is straightforward and neither has to lose. ### FAQ **Q: Can ESPHome work without Home Assistant?** Yes. A device runs standalone with its own web server component, and you can reach it on the local network by IP without any Home Assistant instance. What you lose is the layer Home Assistant provides: history, dashboards across devices, and automations that span more than one board. ESPHome is firmware, not a platform, and the gap is deliberate. **Q: How do I access ESPHome devices outside my WiFi network?** Four practical routes. Reach Home Assistant remotely through Nabu Casa or a VPN and use it as the front door. Put your phone and your network on Tailscale or WireGuard. Run a tunnel to expose a dashboard. Or have the device push its readings outward to an endpoint you control, which needs no inbound path at all. **Q: Does nodrix replace ESPHome?** No. ESPHome is firmware with an excellent configuration model and a large library of supported sensors, and inside the house paired with Home Assistant it is hard to beat. nodrix is a backend. They solve different halves, which is why the useful configuration is often both: ESPHome keeps doing local control, and pushes a copy of the readings outward for remote viewing and alerting. **Q: Can an ESPHome device send data to an external endpoint?** Yes, with the http_request component. It makes HTTPS POST requests with custom headers and a JSON body, validating certificates against ESP-IDF's bundled CA set by default. That is enough to post readings to any endpoint that accepts JSON, which is how you get remote visibility without reflashing to different firmware. **Q: Will pushing data out break my Home Assistant setup?** No. The native API connection to Home Assistant is unaffected — you are adding an outbound POST on an interval, not replacing anything. Local control keeps working exactly as before, including when your internet connection is down, which is the main reason to keep ESPHome doing the local half. --- ## Guide: Meshtastic telemetry to a dashboard you own URL: https://nodrix.live/guides/meshtastic-telemetry-dashboard Category: project Meshtastic shows readings on a screen. How to persist, chart and alert on them without assembling an MQTT, InfluxDB and Grafana stack of your own. Meshtastic solves the hard part. Nodes form a LoRa mesh, relay for each other, and reach places with no Wi-Fi and no cellular — a valley, a ridge, a field several kilometres from anything. What it does not solve is what happens to the readings afterwards. The [telemetry module](https://meshtastic.org/docs/configuration/module/telemetry/) carries battery, voltage, channel utilisation, temperature, humidity and pressure, and you can see all of it on a device screen or in the app. Keeping it, charting it over a season, or being told when a value crosses a line is left to you, and the usual answer is to assemble an MQTT broker, a time-series database and Grafana. There is a shorter path. ## The bridge Meshtastic's [MQTT module](https://meshtastic.org/docs/configuration/module/mqtt/) forwards mesh packets to an external broker, and it has a JSON option that exists specifically to make integration easy. That gives you a clean seam: ``` nodes → mesh → gateway node → MQTT (JSON) → bridge → your deployment ``` **Only the gateway node needs internet.** Everything else reaches it over LoRa, which is the entire reason to run a mesh in the first place. Two caveats worth knowing before you build on it: - **JSON packets are not encrypted.** The documentation says so plainly. Keep the broker on your own network rather than a public one, and treat anything you publish as visible to whoever can read that broker. - **JSON is not supported on nRF52.** If your gateway is an nRF52 node, use an ESP32-based node as the gateway instead. ## Configuring the gateway On the node with connectivity, enable MQTT and turn on JSON output. Point it at your broker — a Mosquitto instance on a Pi is fine — and set a root topic you will recognise. The telemetry module should be enabled on the nodes doing the sensing, with an update interval that suits the airtime you have. LoRa is slow, so readings every fifteen minutes are ordinary and every thirty seconds is usually antisocial on a shared mesh. > **Region settings are not optional** > > Your [`lora.region`](https://meshtastic.org/docs/configuration/radio/lora/) must be set or the > device will not transmit at all — it shows a message on screen and stays silent. The band differs > by region: roughly 902–928 MHz in the US, 869.4–869.65 MHz on EU_868, and 920.5–923.5 MHz in > Japan. **EU_433 and EU_868 are additionally limited to a 10% hourly duty cycle**, calculated every > minute, where most other regions permit 100%. If you are in the EU, that ceiling is the thing that > decides your reporting interval; elsewhere it is courtesy to the mesh rather than law. ## Forwarding to your deployment The bridge is small because both ends speak JSON. It subscribes to the topic, pulls telemetry packets out, and posts them to `/v1/telemetry` with the node name as a prefix so several nodes can share one project: ```python import json import paho.mqtt.client as mqtt import requests BROKER = "192.168.1.50" TOPIC = "msh/+/2/json/#" # root topic from the MQTT module config NODRIX = "https://nodrix.you.workers.dev/v1/telemetry" TOKEN = "tok_your_project_token" def on_message(_client, _userdata, msg): try: packet = json.loads(msg.payload) except ValueError: return if packet.get("type") != "telemetry": return node = packet.get("sender") or str(packet.get("from", "unknown")) payload = packet.get("payload", {}) # Flatten to variables, prefixed per node: cabin_temperature, cabin_battery_level… metrics = { f"{node}_{key}": value for key, value in payload.items() if isinstance(value, (int, float, bool)) } if not metrics: return requests.post( NODRIX, headers={"Authorization": f"Bearer {TOKEN}"}, json={"metrics": metrics}, timeout=10, ) client = mqtt.Client() client.on_message = on_message client.connect(BROKER, 1883, 60) client.subscribe(TOPIC) client.loop_forever() ``` The filter on `int, float, bool` matters. Telemetry payloads carry strings and nested objects alongside the numbers, and passing those through creates variables you will spend an evening deleting. Run it wherever the broker runs — the same Pi is the obvious place — under systemd so it restarts with the machine. ## What you get Variables appear the first time they arrive, so there is no schema to define. On the deployment: - **Charts per node.** `cabin_temperature` and `ridge_temperature` on one graph, over months rather than the last few points held in device memory. - **Battery monitoring that matters.** A remote node's `battery_level` trending down over weeks is the single most useful thing a mesh can tell you, because it predicts the node going silent before it happens. An automation on a threshold turns that into a message. - **Alerts on silence.** Because each node reports on an interval, absence is detectable. A node that stops relaying is a node worth visiting, and you would rather learn that from a notification than from noticing a gap three weeks later. - **A read API** behind one token, if you want the data in Grafana or your own app anyway. - **Shared dashboards** by read-only link, which is how you show a mesh community what the network is doing without giving anyone access to it. ## Why not the usual stack MQTT plus InfluxDB plus Grafana works and plenty of people run it. It is also three services to install, secure, back up and upgrade, on a Pi that is often also the gateway — and InfluxDB's version transitions have been genuinely disruptive for small self-hosted deployments. The bridge above keeps the broker, which you need anyway, and replaces the rest with a deployment in your own Cloudflare account: no database to run, no dashboards to provision, no retention policy to tune, and no machine whose SD card failing takes a season of readings with it. If you already run the full stack and like it, there is no reason to change. If you have been putting it off because it is three days of work before you see a chart, this is an afternoon. ### FAQ **Q: Can Meshtastic send sensor data to a dashboard?** Not directly, but it can publish to MQTT, and that is the bridge. A node with internet access forwards mesh packets to an external MQTT broker, and the module has a JSON option intended for exactly this kind of integration. From there a small bridge forwards the readings to whatever backend you want. **Q: Do all my nodes need internet access?** No, and this is the useful part. Only one node needs connectivity. It acts as the gateway for the mesh, forwarding packets from nodes that may be kilometres away with no internet of their own. That is the whole point of running a mesh: one uplink covers everything reachable through it. **Q: Is the JSON output on Meshtastic MQTT encrypted?** No. The documentation is explicit that JSON packets are not encrypted, which is the tradeoff for easy integration. If that matters, keep the broker on your own network rather than using a public one, and treat what you publish as visible to anyone with access to that broker. **Q: Does this work on every Meshtastic device?** The JSON output is not supported on the nRF52 platform, which rules out some low-power nodes for this specific approach. ESP32-based nodes handle it. If your gateway node is nRF52, use a different node as the MQTT gateway or consume the protobuf output instead. **Q: What sensors does Meshtastic telemetry carry?** The telemetry module covers device metrics such as battery level, voltage and channel utilisation, along with environment metrics including temperature, humidity and pressure, plus air quality and power metrics depending on what is attached. Those arrive as named values you can map straight onto dashboard variables. --- ## Guide: SmartThings API goes paid in October 2026: your options URL: https://nodrix.live/guides/smartthings-api-paid Category: comparison Samsung is ending free SmartThings API access in October 2026. What actually changes, who it affects, and how to own the telemetry layer you were using it for. On [23 June 2026 Samsung announced](https://blog.smartthings.com/smartthings-updates/a-new-enhanced-smartthings-api-experience/) that free SmartThings API access is ending. Individual non-commercial developers move to a **$4.99 USD a month personal plan**; commercial tiers were announced alongside it. Free access remains through Q3 2026, and Samsung said it will not begin applying the new usage limits until **October 2026**. The numbers that would let you plan — the actual rate limits — have not been published. That is the part people are stuck on, and it is why "wait and see" is a worse strategy here than usual. ## What actually changes The change is to the **cloud API**, not to your devices. That distinction decides whether you care. | If you… | Affected? | |---|---| | Use the SmartThings app to control devices | No | | Have Zigbee or Z-Wave devices paired to a local controller | No — local protocols never touch the API | | Run the Home Assistant SmartThings integration | Yes — it talks to the cloud API | | Poll the API to log or graph readings | Yes | | Drive automations or alerts from API data | Yes | | Feed SmartThings data into your own app or dashboard | Yes | The last four are the ones worth thinking about now, because they are also the ones where the dependency is avoidable. ## The honest boundary nodrix does not replace a SmartThings hub, and pretending otherwise would waste your time. A hub speaks Zigbee and Z-Wave, handles pairing, and knows the lifecycle of commercial appliances. That is a real job and nodrix does not do it. If your setup is mostly off-the-shelf plugs, bulbs and sensors, you want a hub — Home Assistant with a Zigbee or Z-Wave stick is the usual answer, and it is a good one. What nodrix does is the layer above: the telemetry, the dashboards, the automations, and the API you read it all back through. If you were calling the SmartThings API because you wanted your readings somewhere you could query, that layer does not have to be rented from anyone. > **The useful question** > > Not "how do I replace SmartThings", but "which parts of this genuinely need a hub, and which parts > are just my own data passing through someone else's cloud on the way to my own dashboard?" ## Your options | Option | What it costs | Good for | |---|---|---| | Pay the $4.99 personal plan | $4.99 USD/month, limits unpublished | Staying exactly as you are | | Move appliances to local control | A Zigbee or Z-Wave stick, one-off | Getting off the cloud API entirely | | Own the telemetry layer | Cloudflare usage, typically nothing | Data you collect yourself | | Some of each | — | Most real setups | The last row is the honest answer for most people. Commercial appliances stay on a hub; the sensors you built, and the data you actually want to keep, stop depending on a subscription. ## Owning the telemetry layer nodrix deploys to your own Cloudflare account. Your hardware reports to it directly over HTTPS or a WebSocket, variables appear the first time they are sent, and the dashboards and automations are yours. There is no per-device pricing and no API plan. A sensor reporting into your own deployment is a few lines. The firmware is deliberately the small part: ```cpp #include #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; const int PUMP_PIN = 2; // LED_BUILTIN on most dev boards, so this runs unwired void setup() { Serial.begin(115200); pinMode(PUMP_PIN, OUTPUT); Nodrix.setFirmwareVersion("1.0.0"); // reported back, so updates can be tracked Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); Nodrix.onDisconnect([] { Serial.println("link lost"); }); } // A dashboard toggle writing back to the device. NODRIX_WRITE(pump) { digitalWrite(PUMP_PIN, value.asBool()); } void loop() { Nodrix.run(); // WebSocket: call every loop static unsigned long last = 0; if (millis() - last >= 30000) { last = millis(); Nodrix.send("uptime_s", (long)(millis() / 1000)); Nodrix.send("rssi", WiFi.RSSI()); } } ``` This runs on a bare board — `uptime_s` and `rssi` need no sensor, and `PUMP_PIN` is the built-in LED so the dashboard toggle is visible straight away. Add your own sensor once the link is proven, and pin TLS with `Nodrix.setCACert()` before you ship: the connection is encrypted but unverified until you do. That is the whole device side. Everything that makes it useful happens on the deployment: - **Dashboards** — drag widgets onto a grid; they stream over a WebSocket, and any dashboard can be shared read-only by link. - **Automations** — variable, schedule, and sunrise/sunset triggers running conditions and actions, including webhooks and chat integrations. - **Read API** — latest state and time-series behind one token, so Grafana or your own app can read it without going through anyone's cloud. ## What to do before October 1. **List what actually calls the API.** Usually fewer things than expected, and usually the data parts rather than the control parts. 2. **Separate hub-dependent from data-only.** Anything Zigbee or Z-Wave paired to a hub stays. Data you generate yourself does not have to. 3. **Move the data-only parts first.** Those are the ones where a subscription buys you nothing you could not hold yourself. 4. **Then decide on the personal plan.** For genuinely hub-dependent setups, $4.99 a month may be the right answer, and there is no shame in paying it. The deadline is worth respecting mostly because the limits are unpublished. Migrating on your own schedule is considerably more pleasant than migrating the week something starts returning errors. ### FAQ **Q: Is the SmartThings API really no longer free?** Samsung announced on 23 June 2026 that free API access ends, with paid tiers launching in October 2026. Individual non-commercial developers move to a $4.99 a month personal plan; commercial tiers were announced separately. Free access was stated to remain available through Q3 2026, and Samsung said it would not begin applying the new usage limits until October. **Q: Does this break my Home Assistant SmartThings integration?** If that integration talks to the SmartThings cloud API, and it does, then it is subject to the same change. Samsung has not published the numeric rate limits that will apply, which is the part most people are waiting on. Local protocols are unaffected: Zigbee and Z-Wave devices paired directly to a local controller never touch the API. **Q: Can nodrix replace my SmartThings hub?** No, and it is worth being plain about that. SmartThings is a hub that speaks Zigbee and Z-Wave to commercial appliances and handles their pairing and lifecycle. nodrix is a backend for hardware you build or control yourself, over HTTPS and WebSocket. If your setup is mostly off-the-shelf smart plugs and bulbs, you need a hub, and Home Assistant with a Zigbee or Z-Wave stick is the usual answer. **Q: So what part can I actually move?** The telemetry and dashboard layer. If you were calling the SmartThings API to log readings, graph history, drive alerts, or feed another app, that is the part you can own outright rather than rent. Your own sensors report to your own deployment, and the data and the dashboards live in your Cloudflare account with no per-device pricing and no API subscription. **Q: How long do I have?** Paid tiers launch in October 2026, so the practical deadline is the end of September. The usage limits that will apply have not been published, which makes planning harder than it should be. If you depend on the API for anything you care about, the sensible move is to know now which parts are genuinely hub-dependent and which parts are just data you could be holding yourself. --- ## Guide: Arduino UNO R4 WiFi to the cloud — a live dashboard with no broker URL: https://nodrix.live/guides/arduino-uno-r4-wifi-cloud Category: hardware · Board: Arduino UNO R4 WiFi Push Arduino UNO R4 WiFi sensor data to a cloud dashboard over HTTPS with WiFiSSLClient — no MQTT broker, no SDK. How the board's dual-processor design makes TLS possible on 32 KB of RAM, and the firmware gotcha that breaks SSL. The UNO R4 WiFi is the first Arduino UNO you can put on the internet without a shield, and it arrives with the thing that made the UNO worth using in the first place: 5V logic, the classic footprint, and a decade of sensors and shields that just work. This guide connects one to a cloud dashboard over plain HTTPS — telemetry up, commands back down, no MQTT broker anywhere. ## The board is two computers This is worth understanding before anything else, because it explains most of the board's behaviour. The UNO R4 WiFi carries **two processors**. Your sketch runs on a **Renesas RA4M1** — an Arm Cortex-M4 at 48 MHz with 32 KB of SRAM and 256 KB of flash. Connectivity is handled entirely by a separate **ESP32-S3**, which does Wi-Fi and Bluetooth LE and nothing of yours. That division is the reason this board can do HTTPS at all. A TLS session wants a substantial record buffer, and 32 KB of SRAM is not much to give it — on a chip that also has to hold your program's variables, it's a genuine squeeze. But the RA4M1 never holds a TLS buffer. The handshake, the ciphers, and the record buffers all live on the ESP32-S3, and your sketch talks to it through a socket-like API. There's an amusing symmetry here that's easy to miss: the Wi-Fi module on your Arduino is the same chip family this site writes about everywhere else. ## What that means in practice Two consequences, one pleasant and one to keep in mind. **You don't manage certificates.** The trust store lives in the ESP32-S3's firmware, so there's no `setCACert`, no PEM blob pasted into your sketch, and no certificate expiry to handle in code. HTTPS to a public host just works. **Your TLS behaviour is tied to a firmware version you update separately.** Because the network stack isn't part of your sketch, updating the Wi-Fi module's firmware can change how SSL behaves without you changing a line — and there is a documented case of an update breaking SSL connections that a downgrade fixed. If working code stops connecting, check the module firmware version before you suspect your own. ## What you'll need - An **Arduino UNO R4 WiFi**. - Any 5V or 3.3V sensor you like — the board's 5V logic means classic Arduino parts need no level shifting. - The **WiFiS3** library, which ships with the UNO R4 board package. - **ArduinoJson**, for parsing the downlink. Sending doesn't need it; receiving does. - A **nodrix instance** with a project and a project token. ## The firmware There's no device library for this board yet, so the sketch speaks the protocol directly. That turns out to be less code than it sounds, because the protocol is a JSON POST with a bearer token and a matching GET for anything queued in the other direction — the same endpoints [the library wraps on an ESP32](https://nodrix.live/guides/esp32-https-cloud). ```cpp #include #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; const int LED_PIN = LED_BUILTIN; WiFiSSLClient client; void connectWiFi() { while (WiFi.begin(WIFI_SSID, WIFI_PASS) != WL_CONNECTED) { delay(3000); } Serial.print("wifi up: "); Serial.println(WiFi.localIP()); } String request(const char* method, const char* path, const String& body, int& status) { status = -1; if (!client.connect(HOST, 443)) return ""; client.print(method); client.print(' '); client.print(path); client.println(" HTTP/1.1"); client.print("Host: "); client.println(HOST); client.print("Authorization: Bearer "); client.println(TOKEN); client.println("Connection: close"); if (body.length()) { client.println("Content-Type: application/json"); client.print("Content-Length: "); client.println(body.length()); } client.println(); if (body.length()) client.print(body); unsigned long t0 = millis(); while (!client.available() && millis() - t0 < 5000) delay(10); String out; if (client.available()) { client.readStringUntil(' '); // "HTTP/1.1" status = client.readStringUntil(' ').toInt(); client.find("\r\n\r\n"); // skip the rest of the headers out = client.readString(); } client.stop(); return out; } void applyWrite(const char* variable, JsonVariantConst value) { if (strcmp(variable, "led") == 0) { digitalWrite(LED_PIN, value.as() ? HIGH : LOW); } } void pollControl() { int status; String body = request("GET", "/v1/control", "", status); if (status != 200) return; JsonDocument doc; if (deserializeJson(doc, body)) return; String ids; for (JsonObjectConst w : doc["control"].as()) { applyWrite(w["variable"].as(), w["value"]); if (ids.length()) ids += ','; ids += '"'; ids += w["id"].as(); ids += '"'; } if (!ids.length()) return; request("POST", "/v1/control/ack", "{\"ids\":[" + ids + "]}", status); } void setup() { Serial.begin(115200); pinMode(LED_PIN, OUTPUT); connectWiFi(); } void loop() { if (WiFi.status() != WL_CONNECTED) connectWiFi(); float temperature = analogRead(A0) * (5.0 / 1023.0) * 100.0; // swap for a real sensor int status; request("POST", "/v1/telemetry", String("{\"metrics\":{\"temperature\":") + temperature + "}}", status); Serial.print("POST /v1/telemetry -> "); Serial.println(status); // 204 = accepted pollControl(); delay(30000); } ``` `client.stop()` on every path is the line that matters most on this board. The ESP32-S3 holds a small number of sockets, and a sketch that leaks them stops sending after a while with no error to explain it — the classic symptom being a build that works for an hour and then silently goes quiet. Note the asymmetry in how JSON is handled. The telemetry body is assembled by hand with `String` concatenation, because a few sensor values don't justify a parser on a 32 KB board. The control response *is* parsed with ArduinoJson, because you're reading a structure someone else produced and hand-scanning it for quotes is how you get a board that misbehaves on an unexpected value. ## Receive commands The downlink is a poll rather than a push: no broker holding a subscription open, no socket to keep alive. The board asks `/v1/control` whether anything is waiting, applies what it finds, and acknowledges each write by id. That acknowledgement is the step worth not skipping. Unacked writes are offered again on the next poll, so a board that applies a command without acking reapplies it forever — which presents as a relay that refuses to stay switched. To try it, add a **toggle** widget bound to a variable named `led`. Flipping it queues a write the sketch collects on its next pass and applies to the onboard LED, which is the fastest way to prove both directions work before you wire anything up. The trade is latency. This sketch polls once per `loop()`, so a command waits up to thirty seconds. That's the usual starting point, and the [downlink pattern in general](https://nodrix.live/guides/esp32-receive-commands) covers when it's worth tightening. ## Build the dashboard Open your project and `temperature` is already listed — variables are created the first time they're seen. Drop a **value** or **gauge** widget on it, or a **chart** to watch it move. Send several keys in one POST and each becomes its own variable. From there a **variable** trigger turns any reading into a message — the [notifications guide](https://nodrix.live/guides/esp32-notifications) covers Telegram, Discord, Slack, and SMS from the same automation. ## Use the LED matrix The R4 WiFi has a 12×8 LED matrix on board, which is genuinely useful here rather than decorative: it gives the board a way to tell you what it's doing without a serial cable attached. With the `Arduino_LED_Matrix` library, showing connection state or the last HTTP status is a few lines — `matrix.loadFrame()` with a pattern for connected, another for a failed POST. For a device sitting on a shelf, glanceable status beats a serial monitor you aren't watching. ## Notes The `analogRead` in the example uses the board's default 10-bit resolution for compatibility with UNO-era code. The RA4M1's ADC does better — `analogReadResolution(14)` unlocks it, and it's one of the quiet upgrades over the R3 worth knowing about. Deep sleep on this board is not comparable to an ESP32's. If your project needs to run for months on a battery, that's a genuine reason to choose different hardware; the R4 WiFi is at its best on mains power or a large pack. The board also has a real DAC and CAN bus, neither of which the R3 had. Neither is needed here, but they're the reason this board shows up in projects an UNO couldn't previously do. Both processors can be updated independently. Keeping the Wi-Fi module's firmware current is generally right, but note the version you're on when things work — it's the one dependency of this board that isn't visible in your sketch. ### FAQ **Q: How does a board with 32 KB of RAM manage HTTPS?** It doesn't, strictly — the other processor does. The UNO R4 WiFi carries a Renesas RA4M1 running your sketch and an ESP32-S3 handling all connectivity, and the TLS session lives on the ESP32-S3 side. That division is what makes the numbers work: a TLS record buffer alone would consume most of the RA4M1's 32 KB, but your sketch never has to hold one. **Q: Do I need to manage certificates?** No, and that's a genuine convenience. Because TLS terminates on the ESP32-S3, the trust store lives in that module's firmware rather than in your sketch — there's no `setCACert` call and no PEM blob to paste in. The trade is that your TLS behaviour is determined by a firmware version you update separately from your code. **Q: My HTTPS connection suddenly stopped working. What changed?** Check the Wi-Fi module's firmware version before you debug your sketch. Because the network stack lives in the ESP32-S3, its firmware updates can change SSL behaviour independently of anything you wrote — there's a well-documented case of an update breaking SSL connections that a downgrade resolved. It's an unusual failure mode and the first thing to rule out when working code stops working. **Q: Is the UNO R4 WiFi better than an ESP32 for IoT?** It's better at some things and behind at others. It wins on 5V logic, so classic Arduino shields and 5V sensors work without level shifting, and on the UNO form factor and its ecosystem. The ESP32 wins on RAM by a wide margin, on deep-sleep power, and on the depth of cloud-connected example code. For a first connected project on hardware you already own, the R4 is genuinely fine. **Q: Does the nodrix Arduino library work on this board?** Not yet — it's built for ESP32 and ESP8266. It matters less than you'd expect, because the device protocol is deliberately plain HTTPS: a JSON POST with a bearer token, which is about fifteen lines against WiFiSSLClient. Renesas support is on the roadmap; until then this guide shows the direct approach. --- ## Guide: A Claude Skill for ESP32: teach Claude to write firmware for your hardware URL: https://nodrix.live/guides/claude-skill-for-esp32 Category: concept · Board: ESP32 Build a Claude Skill that makes Claude write correct ESP32 firmware for your own IoT backend — what a Skill is, how it differs from an MCP server, and a complete SKILL.md you can drop in today. Ask Claude to write ESP32 firmware and you'll get something plausible: a sketch built on whichever IoT library was most common in its training data, with your API half-remembered. It compiles. It's wrong in small ways that take an evening to find. The fix isn't a better prompt. It's giving Claude the actual reference material, permanently, in a form it loads on its own. That's what a Skill is. ## What a Skill actually is A Skill is a folder with a `SKILL.md` inside it. The file has YAML frontmatter with a **name** and a **description**, and a markdown body of instructions. The mechanism is simpler than it sounds. Claude reads the description of every available skill, and when a task looks like a match, it loads that skill's body into context. You don't invoke it. You ask for what you want, and the relevant knowledge arrives with the request. Skills live in `~/.claude/skills//` for ones you want everywhere, or `.claude/skills//` inside a repository for ones belonging to that project. Larger skills can add a `references/` directory of supporting documents that get pulled in only when needed, so the main file stays short. ## Skill or MCP server? Both put Claude and your hardware together, and they're not alternatives — they solve opposite halves. An **MCP server** gives Claude **tools it can call**. Nodrix ships one: Claude can read your live variable state, set values, create dashboards and automations, and fire events against your running instance. That's covered in [Control your ESP32 with Claude](https://nodrix.live/guides/control-esp32-with-claude-mcp). It changes what Claude can *do*. A **Skill** gives Claude **knowledge it applies**. No tools, no network calls — just the correct API surface, your conventions, and the mistakes worth avoiding. It changes what Claude *knows*. The combination is where this gets genuinely useful. The Skill means the sketch it writes uses the right calls in the right order. The MCP server means it can then look at your dashboard and confirm the readings arrived. Write, flash, verify — without you translating between the two halves. ## Writing the skill The description field deserves more care than the body, because it's the only part Claude sees when deciding whether to load the skill. Write it as triggers, not as a summary: name the boards, the libraries, the file types, and the tasks. "Helps with IoT projects" will never fire; the version below names ESP32, Arduino, telemetry, and the library. The body should encode what Claude gets wrong unaided — exact signatures, the ordering constraints, and the traps. Here's a complete skill for ESP32 work against a nodrix instance: ```markdown --- name: nodrix-esp32 description: Write ESP32 or ESP8266 firmware that talks to a nodrix IoT instance. Use when writing or reviewing Arduino sketches (.ino/.cpp) that send telemetry, handle control writes, use the Nodrix library, or connect hardware to a nodrix dashboard. Covers Nodrix.send, NODRIX_WRITE handlers, deep-sleep HTTP mode, and the raw HTTPS protocol for unsupported boards. --- # nodrix ESP32 firmware ## Library API (ESP32 / ESP8266) - `Nodrix.begin(ssid, pass, host, token)` — WebSocket mode; call `Nodrix.run()` every `loop()`. - `Nodrix.begin(host, token)` — same, but assumes Wi-Fi is already connected. Use this after WiFiManager or any other provisioning. Also call `Nodrix.addAP(ssid, psk)` afterwards, or the library cannot reconnect on its own when the link drops. - `Nodrix.beginHTTP(ssid, pass, host, token)` — polling mode for deep-sleep devices; call `Nodrix.poll()` once per wake instead of `run()`. - `Nodrix.send(key, value)` — overloads for bool, int, long, float, double, const char*, String. - `Nodrix.flush()` — sends queued telemetry now. REQUIRED before deep sleep or a long blocking operation, or queued readings are lost. - `Nodrix.event(name)` — fires an event automations can trigger on. - `Nodrix.setCACert(pem)` for pinned TLS; `setInsecure()` is development only. ## Control writes (cloud to device) Register handlers with the macro, at file scope — not inside setup(): NODRIX_WRITE("relay") { digitalWrite(RELAY_PIN, value.asBool() ? HIGH : LOW); Nodrix.send("relay", value.asBool()); // echo real state so dashboards hydrate correctly } `value` is a NodrixValue: `asBool()`, `asInt()`, `asLong()`, `asFloat()`, `asDouble()`, `asString()`, `isNull()`. Always echo the actual hardware state back after applying a write. ## Raw protocol (boards the library does not support) - `POST /v1/telemetry` body `{"metrics": {"key": value}}` -> 204. Values are number, string, boolean, or null. Variables are created on first sight; never register them in advance. - `GET /v1/control` -> `{"control": [{"id","variable","value"}]}` - `POST /v1/control/ack` body `{"ids": ["ctl_..."]}` -> `{"acked": n}`. Unacked writes are resent. - `POST /v1/events` body `{"event": "name"}` -> 204 - Auth is `Authorization: Bearer ` on all of the above. ## Rules 1. Never send a placeholder value on a failed sensor read. Skip the send — a gap is honest, a zero looks like a real reading and will fire alerts. 2. Put thresholds and alert logic in cloud automations, not in firmware. Reflashing a deployed board to change a number is the problem this architecture exists to avoid. 3. Use `millis()` interval checks, never `delay()`, in WebSocket mode — `Nodrix.run()` must be called continuously. 4. Enabling OTA requires a partition scheme with two app slots. "Huge APP" has none. ``` ## Using it Drop that in `.claude/skills/nodrix-esp32/SKILL.md`, and the next time you ask for a sketch that reports a sensor reading, the skill loads without being mentioned. The difference is immediate: correct signatures, `flush()` before sleep, handlers at file scope, and no invented API. The rules section is where the real value accumulates. Every trap this site has documented — sending zeros on sensor failure, baking thresholds into firmware, the [OTA partition mistake](https://nodrix.live/guides/esp32-ota-updates) — becomes something Claude won't walk into. When you find a new one, add a line. ## Going further Split large skills. If your project has a lot of hardware-specific detail, keep `SKILL.md` short and put the depth in `references/` files it can pull in when relevant — the same pattern the official skills use. Make it project-specific. A skill committed to a repository can encode that project's pin assignments, its variable naming, and its build commands, which is knowledge no general-purpose assistant could have. Pair it with the [MCP server](https://nodrix.live/guides/control-esp32-with-claude-mcp) for the full loop. With both in place, "add a humidity sensor to the greenhouse board and check it's reporting" is a single request rather than three sessions of copy-paste. ## Notes Skills are markdown, so version them like code. A skill in the repository means everyone working on the project — including future you — gets the same conventions applied automatically. Keep the body focused on what Claude gets wrong unaided. Restating general Arduino knowledge wastes context; the API surface, the ordering constraints, and your project's specific traps are what earn their place. A skill that never seems to load almost always has a description problem, not a body problem. Add the concrete nouns — board names, library names, file extensions — that appear in the requests you actually make. ### FAQ **Q: What's the difference between a Claude Skill and an MCP server?** A Skill is knowledge; an MCP server is capability. A Skill is a folder of instructions Claude loads when a task looks relevant, so it changes what Claude *knows* — the right API calls, your conventions, the traps. An MCP server exposes tools Claude can *call*, so it changes what Claude can *do* — read a live sensor, flip a relay. For hardware work you want both: the Skill writes the firmware correctly, the MCP server checks whether it actually worked. **Q: Why not just paste the API docs into the chat?** Because you'd do it every session, and you'd forget. A Skill loads automatically when the work matches its description, applies to every conversation without being asked, and lives in version control alongside your project. Pasting context works once; a Skill is the same thing made durable. **Q: Where does the skill file go?** A folder containing `SKILL.md` — under `~/.claude/skills//` for skills you want everywhere, or `.claude/skills//` inside a repository for ones that belong to that project. Committing it to the repo is usually right for hardware work, because the conventions it encodes are the project's, not yours personally. **Q: What makes a skill actually get used?** The description field, which is the only part Claude sees when deciding whether to load it. Write it as a list of triggers rather than a summary — name the libraries, the boards, the file types, and the tasks. A description reading 'helps with IoT' will sit unused; one naming ESP32, Arduino, telemetry, and your library will fire when it should. **Q: Can Claude flash the board too?** Through the skill alone, no — a Skill is instructions, not tools. But Claude Code can run shell commands, so a skill that documents your build and flash commands effectively gives it that ability. Pair it with an MCP server on your instance and the loop closes: write the sketch, flash it, then read the telemetry back to confirm the board is actually reporting. --- ## Guide: Bridge cheap BLE sensors to your own cloud with an ESP32 URL: https://nodrix.live/guides/esp32-ble-sensor-gateway Category: project · Board: ESP32 Turn five-dollar Bluetooth thermometers into a monitored sensor network: passive BLE scanning on an ESP32, parsing the open BTHome v2 broadcast format, and the stack choice that decides whether BLE and Wi-Fi fit on one chip at all. There is a category of hardware that's almost too cheap to ignore: small BLE sensors that cost about as much as a coffee and run for a year on a coin cell. Temperature and humidity in every room for the price of one decent sensor. What they don't come with is anywhere to put the data. This build turns an ESP32 into a gateway that listens for them and forwards their readings to your own dashboard — no pairing, no vendor app, and no cloud but yours. ## Why this works: advertisements, not connections The thing that makes a one-to-many gateway possible is that you never connect to anything. BLE devices broadcast **advertisement packets** to announce themselves, and sensors of this kind put their actual readings inside those packets. Temperature, humidity, and battery are in the broadcast itself. Your gateway sits and listens. That has consequences worth appreciating. There's no pairing, no bonding, and no connection limit — so a single ESP32 can watch every sensor in range at once, and adding a tenth sensor needs no gateway change whatsoever. It's also why the sensors last so long on a coin cell: broadcasting briefly costs far less than maintaining a connection. ## The stack choice that decides whether this fits Before any code: if you add BLE to a Wi-Fi project and it crashes on boot or fails to allocate, this is why. The ESP32's default Bluetooth stack is **Bluedroid**, designed to handle Bluetooth Classic and BLE together. Even with Classic disabled, that machinery is still compiled in and still resident. **NimBLE** was designed as BLE-only from the start and drops all of it. The saving is not marginal. NimBLE uses roughly **50% less flash and around 100 KB less RAM** for the same functionality. On a gateway that must simultaneously hold Wi-Fi buffers, a TLS session, and a BLE scanner, 100 KB is frequently the entire margin between working and not. Use NimBLE. There's no scenario in this build where Bluedroid is the better choice. ## BTHome: the format worth targeting Historically, reading these sensors meant reverse-engineering a manufacturer's packet layout, and the maker community produced several custom formats — ATC and PVVX among them — for the Xiaomi thermometers. **BTHome v2** replaced that mess with an open, documented, properly registered format, and it's what you should target now. This isn't just preference: the custom-firmware maintainers have said support for non-standard, unregistered advertising formats is being dropped in favour of BTHome v2 only. Writing a parser for the older formats is work you'd do again shortly. The layout is refreshingly simple. Data arrives as **service data under UUID 0xFCD2**. The first byte is a device-information byte — **0x40** meaning BTHome v2, unencrypted, regular updates. After that it's a stream of measurements, each an object ID followed by its value, little-endian: | Object ID | Property | Type | Factor | |---|---|---|---| | `0x00` | packet id | uint8 | 1 | | `0x01` | battery | uint8 | 1 (%) | | `0x02` | temperature | sint16 | 0.01 | | `0x03` | humidity | uint16 | 0.01 | | `0x0C` | voltage | uint16 | 0.001 | ## What you'll need - An **ESP32** dev board on mains power, positioned centrally. - One or more **BTHome v2 sensors** — Xiaomi LYWSD03MMC thermometers reflashed with custom firmware are the cheapest route, and the flashing is done from a web browser over Bluetooth. - The **NimBLE-Arduino** (2.x) and **Nodrix** libraries. - A **nodrix instance** with a project and a project token. ## The firmware A passive scan, a BTHome parser, and a forward. Passive rather than active scanning is deliberate: active scanning sends scan requests back to devices, which wastes power on both ends and gains nothing when the data is already in the broadcast. ```cpp #include #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; static const NimBLEUUID BTHOME_UUID((uint16_t)0xFCD2); String shortName(const NimBLEAddress& addr) { String s = addr.toString().c_str(); s.replace(":", ""); return "s" + s.substring(8); // last 2 bytes -> stable per-sensor prefix } void parseBTHome(const uint8_t* d, size_t len, const String& id) { if (len < 1 || d[0] != 0x40) return; // v2, unencrypted size_t i = 1; while (i < len) { uint8_t obj = d[i++]; switch (obj) { case 0x00: i += 1; break; // packet id case 0x01: Nodrix.send(id + "_battery", (int)d[i]); i += 1; break; case 0x02: { int16_t raw = (int16_t)(d[i] | (d[i + 1] << 8)); Nodrix.send(id + "_temperature", raw * 0.01f); i += 2; break; } case 0x03: { uint16_t raw = (uint16_t)(d[i] | (d[i + 1] << 8)); Nodrix.send(id + "_humidity", raw * 0.01f); i += 2; break; } case 0x0C: { uint16_t raw = (uint16_t)(d[i] | (d[i + 1] << 8)); Nodrix.send(id + "_voltage", raw * 0.001f); i += 2; break; } default: return; // unknown id: length unknown, stop safely } } } class ScanCB : public NimBLEScanCallbacks { void onResult(const NimBLEAdvertisedDevice* dev) override { if (!dev->haveServiceData()) return; std::string sd = dev->getServiceData(BTHOME_UUID); if (sd.empty()) return; parseBTHome((const uint8_t*)sd.data(), sd.size(), shortName(dev->getAddress())); } } scanCB; void setup() { Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); NimBLEDevice::init(""); NimBLEScan* scan = NimBLEDevice::getScan(); scan->setScanCallbacks(&scanCB); scan->setActiveScan(false); // passive: listen only scan->setInterval(100); scan->setWindow(99); scan->start(0, false); // scan forever } void loop() { Nodrix.run(); delay(10); } ``` The `default: return` in the parser matters more than it looks. BTHome object IDs have varying data lengths, so meeting an ID you don't handle means you no longer know where the next one starts — continuing would misread the rest of the packet as plausible-looking nonsense. Stopping is the only safe response. Deriving the variable prefix from the last bytes of the MAC address gives each sensor stable, automatic naming. Put a thermometer in a new room and `s4f2a_temperature` appears on its own; nothing in the gateway needs editing. ## Build the dashboard Every sensor creates its own variables on first broadcast, so a **chart** widget per room is the natural layout, and several temperature series on one chart is where this gets genuinely useful — comparing rooms shows you things a single sensor never will. The battery variables deserve one **chart** between them. Coin cells fade slowly and predictably, and a year of declining voltage tells you which sensor to attend to before it goes silent, rather than after. ## Going further Add a **variable** trigger on any room's temperature to catch a heating failure, or on a battery level below 15% so replacements are planned rather than discovered — the [notifications guide](https://nodrix.live/guides/esp32-notifications) covers routing those to Telegram, Discord, Slack, or SMS. The same gateway can carry a wired sensor of its own. It's an ordinary ESP32 with an idle I2C bus, so a [BME280 on the gateway](https://nodrix.live/guides/esp32-weather-station) gives you a trustworthy reference reading alongside the cheap broadcasters — useful for spotting one that has drifted. If you also run [Home Assistant](https://nodrix.live/guides/home-assistant-vs-nodrix), note that it speaks BTHome natively. Both can listen to the same sensors simultaneously without conflict, because broadcasts are one-to-many by nature — local control in one place, history and remote dashboards in the other. ## Notes Wi-Fi and BLE share one 2.4 GHz radio, so the chip time-slices between them and your scanner misses some advertisements. This is normal rather than a fault: sensors rebroadcast every few seconds, and a missed packet costs nothing. Passive scanning cannot see devices that only reveal data on connection. If a sensor shows up in a scan but carries no service data, it's likely one that requires a connection — a different and much less scalable job than this build. BTHome supports encrypted broadcasts, which this parser doesn't handle. If you enable encryption on your sensors, the gateway needs the bind key and a decryption step before parsing. ### FAQ **Q: Do I have to pair with each sensor?** No, and that's what makes this practical. Cheap BLE sensors broadcast their readings in advertisement packets — the same packets that announce a device exists — so a gateway just listens. No pairing, no bonding, no connection limit. One ESP32 can watch as many sensors as are in range, and adding another sensor requires no gateway change at all. **Q: Why does my sketch crash when I add BLE to a Wi-Fi project?** You're almost certainly out of RAM. The default Bluedroid stack was built to handle Bluetooth Classic alongside BLE, and it carries that weight even when Classic is disabled. Switching to NimBLE frees roughly 100 KB of RAM and about half the flash — which on a chip that also has to hold Wi-Fi buffers and a TLS session is frequently the difference between a build that runs and one that doesn't. **Q: Which sensors work with this?** Anything broadcasting BTHome v2, which is an open, documented format rather than a reverse-engineered one. The five-dollar Xiaomi LYWSD03MMC thermometers are the classic candidates — custom firmware flashes onto them from a web browser over Bluetooth and makes them broadcast BTHome. Purpose-built BTHome sensors and DIY beacons work identically. **Q: Should I use the ATC or PVVX advertisement format instead?** Target BTHome v2 for anything new. Those older custom formats work today, but the firmware maintainers have stated that support for non-standard, unregistered advertising formats is being dropped in favour of BTHome v2 only. Writing a parser for a format that's being retired is work you'll do twice. **Q: Will scanning for BLE interfere with Wi-Fi?** They share one 2.4 GHz radio, so the chip time-slices between them and each gets less than its full attention. In practice a gateway still catches plenty of advertisements, because sensors broadcast repeatedly and missing one costs you nothing. It does mean you should treat a missed reading as normal rather than as a fault to engineer around. --- ## Guide: Build an ESP32 e-paper dashboard that runs for months on a battery URL: https://nodrix.live/guides/esp32-epaper-dashboard Category: project · Board: ESP32 A complete ESP32 e-ink dashboard: pull live sensor values from your own cloud read API, render them on a Waveshare panel, and deep sleep between updates — with the refresh discipline that keeps an e-paper display from ghosting itself into a permanent mess. Every other build on this site pushes data up. This one pulls it back down: a small e-ink panel on a shelf showing the readings your sensors are already collecting — [tank level](https://nodrix.live/guides/esp32-water-tank-monitor), [freezer temperature](https://nodrix.live/guides/esp32-freezer-alarm), [battery state of charge](https://nodrix.live/guides/esp32-solar-battery-monitor) — updated quietly, with no glow, no fan, and no cable. E-paper is the right technology for it for one specific reason: it draws power only while the image is changing. A panel showing yesterday's numbers on a dead battery looks exactly like a panel showing today's, which is both its superpower and, as you'll see, something to design around. ## What you'll build An ESP32 that wakes every fifteen minutes, fetches your project's current state from the read API, draws it, puts both the panel and itself back to sleep, and runs for months on a single cell. ## The two ways to ruin an e-paper panel Worth covering before the wiring, because both are permanent and both are easy to walk into. **Ghosting from partial refreshes.** E-paper supports two update modes. A full refresh flashes the panel black and white a few times and leaves a perfectly clean image; a partial refresh updates quietly and quickly but leaves faint residue of what was there before. Partial updates are what make e-paper feel usable — and if you only ever do partial updates, that residue accumulates. Manufacturer guidance is a **full refresh every five to ten partial ones**, and the warning attached is stronger than cosmetic: left unchecked, ghosting can degrade the panel rather than merely look bad. **Leaving the panel powered and idle.** When an e-paper display isn't refreshing, it must be put into sleep mode or powered down. Left powered and static, the panel sits in a high-voltage state that damages the film, and that damage is not recoverable. Waveshare also advises against holding the same static image for very long periods. Both have the same one-line answers, and the sketch below applies them: count your partial refreshes and periodically do a full one, and call `hibernate()` before you sleep. ## What you'll need - An **ESP32** dev board — any common DevKit variant. - A **Waveshare or Good Display e-paper panel** — a 2.9" black-and-white module is a good starting size, and supports partial refresh. - A **LiPo or 18650 cell** and a way to charge it, if you want it cable-free. - The **GxEPD2**, **ArduinoJson**, and **WiFiClientSecure** Arduino libraries. - A **nodrix instance**, and an **API token** — not a project token, since this build reads. ## Wiring E-paper modules are SPI with three extra control lines: | From | To | Wire | |------|----|------| | Panel VCC | ESP32 3V3 | Power | | Panel GND | ESP32 GND | Ground | | Panel DIN | ESP32 GPIO23 | SPI MOSI | | Panel CLK | ESP32 GPIO18 | SPI clock | | Panel CS | ESP32 GPIO5 | Chip select | | Panel DC | ESP32 GPIO17 | Data/command | | Panel RST | ESP32 GPIO16 | Reset | | Panel BUSY | ESP32 GPIO4 | Busy (input) | `RST` matters more than it looks on a sleeping build: the library wakes a hibernated panel by toggling reset, so that line has to be a real GPIO rather than tied high. ## The firmware The flow is linear because the board is awake for only a few seconds: connect, fetch, draw, hibernate the panel, deep sleep. There is no `loop()` worth speaking of. Two details make it work across sleep cycles. The refresh counter lives in `RTC_DATA_ATTR` memory, which survives deep sleep while normal variables don't — that's how a board that reboots every fifteen minutes still knows it has done nine partial refreshes. And the state fetch uses the read API, which returns every variable's latest value in one request. ```cpp #include #include #include #include #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "https://nodrix.you.workers.dev"; const char* PROJECT = "proj_your_project_id"; const char* API_TOKEN = "tok_your_api_token"; // user/API token, not a project token const uint64_t SLEEP_US = 15ULL * 60 * 1000000; const int FULL_EVERY = 10; // full refresh every N updates // Match this class to your exact panel. GxEPD2_BW display( GxEPD2_290_T94(/*CS*/ 5, /*DC*/ 17, /*RST*/ 16, /*BUSY*/ 4)); RTC_DATA_ATTR int refreshCount = 0; // survives deep sleep bool fetchState(JsonDocument& doc) { WiFiClientSecure client; client.setInsecure(); // pin a CA for production HTTPClient http; String url = String(HOST) + "/v1/projects/" + PROJECT + "/state"; http.begin(client, url); http.addHeader("Authorization", String("Bearer ") + API_TOKEN); int code = http.GET(); bool ok = (code == 200) && (deserializeJson(doc, http.getStream()) == DeserializationError::Ok); http.end(); return ok; } void drawRow(int y, const char* label, JsonVariant v, const char* unit) { display.setCursor(4, y); display.print(label); display.setCursor(150, y); if (v.isNull()) display.print("--"); else display.print(v.as(), 1); display.print(unit); } void setup() { WiFi.begin(WIFI_SSID, WIFI_PASS); for (int i = 0; i < 40 && WiFi.status() != WL_CONNECTED; i++) delay(250); JsonDocument doc; bool ok = (WiFi.status() == WL_CONNECTED) && fetchState(doc); JsonObject state = doc["state"]; display.init(); bool full = (refreshCount % FULL_EVERY == 0); display.setPartialWindow(0, 0, display.width(), display.height()); display.setRotation(1); display.setTextColor(GxEPD_BLACK); display.setTextSize(2); display.firstPage(); do { display.fillScreen(GxEPD_WHITE); if (!ok) { display.setCursor(4, 20); display.print("offline"); } else { drawRow(20, "Tank", state["tank_level"]["value"], "%"); drawRow(50, "Freezer", state["freezer_temp"]["value"], "C"); drawRow(80, "Battery", state["battery_voltage"]["value"], "V"); } } while (display.nextPage()); if (full) display.clearScreen(); // periodic ghost clear refreshCount++; display.hibernate(); // never skip this esp_deep_sleep(SLEEP_US); } void loop() { } ``` The `offline` branch is deliberate. A dashboard that fails to fetch and redraws the previous numbers is actively misleading, because e-paper's persistence means stale data looks exactly as authoritative as fresh data. Better to say plainly that the reading is unavailable. `setInsecure()` keeps the example short. For anything that lives outside your own network, pin the CA certificate instead — the [HTTPS guide](https://nodrix.live/guides/esp32-https-cloud) covers how. ## Choosing what to display E-paper rewards restraint. A 2.9" panel holds maybe four values at a size readable across a room, and a dashboard of four numbers you actually check beats twelve you skim past. The values worth a permanent display are the ones with a slow, meaningful state: tank percentage, freezer temperature, battery voltage, today's generated amp-hours. Things that change second to second belong on a phone, not on a panel that shouldn't refresh more than every few minutes anyway. ## Going further Add a sparkline by calling the series endpoint — `/v1/projects/:proj/variables/:key/series?window=24h` returns recent points, and a handful of `drawLine` calls turns them into a trend. A number plus its last day of history is a disproportionately better dashboard than the number alone. For genuinely minimal power, cut the panel's supply entirely with a MOSFET on a GPIO. Even hibernated, an e-paper module draws a small residual current from its VCC line, which matters over months even though it's negligible over hours. A three-colour panel adds red or yellow, which is tempting for alarm states. Note the trade before committing: three-colour panels have no partial refresh and take considerably longer to update, so every refresh is a full, flashing one. ## Notes `RTC_DATA_ATTR` is what makes the refresh counter work. Ordinary globals are reinitialised on wake from deep sleep, because the chip genuinely reboots — only RTC memory survives. The `GxEPD2_290_T94` class in the example matches one specific 2.9" panel. GxEPD2 supports dozens of panels and picking the wrong class typically produces a blank or garbled display rather than an error, so check the library's examples against the exact module you bought. Waveshare recommends a minimum refresh interval of around 180 seconds. At fifteen-minute updates this build sits comfortably inside that, but it's worth knowing before you decide a one-minute clock would be nicer. E-paper refreshes poorly at low temperatures and the effect starts well above freezing. A panel in an unheated garage in winter may update slowly or unevenly, which is a property of the film rather than anything wrong with the build. ### FAQ **Q: Why does my e-paper display look faded or show old text?** That's ghosting, and it accumulates when you only ever do partial refreshes. Partial updates are fast and don't flash, but they leave residue from the previous image, and manufacturers are clear that letting it build up can eventually damage the panel rather than just look bad. The fix is discipline: a full refresh every five to ten partial ones clears it, and this build tracks that count across deep sleep. **Q: How long does a battery actually last?** Months, because e-paper holds its image with no power at all — the display only draws current during the second or two it's changing. With an ESP32 in deep sleep between updates, a 2000 mAh cell runs roughly three to five months at fifteen-minute updates, and past a year at hourly ones. The refresh rate is the dominant variable, not the panel size. **Q: Do I need to put the panel to sleep too?** Yes, and this is the one that permanently kills displays. An e-paper panel left powered without refreshing sits in a high-voltage state, and manufacturers warn that leaving it there damages the film irreparably. Call the library's hibernate function after every update, before the ESP32 sleeps. It is one line and it is not optional. **Q: Why does this use an API token instead of a project token?** Because it's reading, not writing. Project tokens are for hardware pushing telemetry up and receiving control writes back; the read API that serves current values to a consumer is authenticated with a user or API token instead. This display is a client of your data rather than a source of it, so it uses the client credential. **Q: How often can I refresh an e-paper display?** Less often than you'd like. Waveshare suggests a minimum interval of around 180 seconds, and that's guidance about panel longevity rather than a technical limit. It suits this application anyway — a dashboard of temperatures and levels that updates every fifteen minutes reads as live, and refreshing every few seconds would burn a battery and the panel for no benefit. --- ## Guide: Build an ESP32 fridge and freezer alarm that survives a power cut URL: https://nodrix.live/guides/esp32-freezer-alarm Category: project · Board: ESP32 A complete ESP32 freezer temperature alarm on the DS18B20: monitor fridge and freezer from one probe cable, alert on a sustained rise rather than a door opening, and keep the alarm alive when the power that killed the freezer would have killed the board too. A freezer fails quietly. The compressor stops, the light still comes on when you open the door, and nothing looks wrong for about eight hours — by which point a freezer full of food is finished. The whole value of a freezer alarm is catching that in hour one, from wherever you happen to be. This build puts a probe in the freezer and another in the fridge, streams both to a live dashboard, and alerts on a sustained temperature rise. It also deals with the failure mode most DIY freezer monitors quietly ignore: what happens when the power cut that killed the freezer kills the monitor too. ## The design problem worth solving first Think about how a freezer actually fails. There are two cases and they need different answers. **The compressor dies, or the door is left ajar.** The freezer is still powered, so a monitor plugged in beside it is still powered too. It watches the temperature climb and raises the alarm. Easy. **The power goes out.** The freezer stops — and so does the ESP32 you plugged into the wall next to it. A board with no power reports nothing, and *nothing* looks identical to *everything is fine*. This is the failure that ruins freezers, and it's the one a naive build is blind to. The fix is cheap: run the ESP32 from a small USB power bank that charges from the wall. Mains drops, the freezer stops, the board keeps running for hours, and it reports the rise while you can still move food. An ESP32 draws little enough that a modest power bank covers a long outage. That still leaves the router, which also died. If the outage is local to your home, a phone hotspot gets the board online; if you want certainty, the honest answer is that a truly power-independent alarm needs its own uplink, which is beyond a Wi-Fi build. One more layer helps: a **schedule** automation that messages you the temperature once a day. A daily message that *arrives* proves the whole chain is alive. Silence, from a system that should have spoken, is itself information. (A trigger that fires when a variable simply stops updating is on the roadmap; until then the daily heartbeat is the way to get the same reassurance.) ## Why the DS18B20 — and what it can't do down there The DS18B20 is the right sensor for this, mostly for packaging reasons. It comes in a sealed stainless probe on a cable, it's digital so cable length doesn't degrade the reading, and it's 1-Wire — every part carries a unique 64-bit address, so several probes share one GPIO and the board distinguishes them by address rather than by pin. Its accuracy needs stating plainly, because the number everyone quotes doesn't apply here. The famous **±0.5°C is specified from -10°C to +85°C**. Below that the datasheet tolerance widens to roughly **±2.0°C**, which covers the entire useful range of a freezer. For this application that's perfectly acceptable, and it's worth being clear why: you are detecting a failure that moves the temperature by ten or twenty degrees, not auditing whether it sat at -18°C or -19°C. If you need the exact number — food safety logs, laboratory samples — calibrate each probe against a reference at freezer temperature and store the offset. ## What you'll need - An **ESP32** dev board — any common DevKit variant. - Two **waterproof DS18B20 probes** on cable (one per compartment). - One **4.7 kΩ resistor** as the 1-Wire bus pullup. - A small **USB power bank** that can charge and supply at once, for the outage case. - The **Nodrix**, **OneWire**, and **DallasTemperature** Arduino libraries. - A **nodrix instance** with a project and a project token. ## Wiring Both probes land on the same three connections — that's the point of 1-Wire: | From | To | Wire | |------|----|------| | DS18B20 VDD (red) | ESP32 3V3 | Power | | DS18B20 GND (black) | ESP32 GND | Ground | | DS18B20 DATA (yellow) | ESP32 GPIO4 | 1-Wire bus | | DATA | 3V3 | 4.7 kΩ pullup | The pullup is mandatory, not optional — 1-Wire is an open-drain bus and without it you read nothing at all. One resistor serves the whole bus no matter how many probes hang off it. Power the probes properly from 3V3 rather than using parasite power. Parasite mode saves a wire and becomes unreliable exactly where this build lives: long cable runs and low temperatures. The third wire is free; use it. Route each cable through the door gasket on the hinge side. Modern magnetic seals close over a thin probe cable without losing their seal, and it's how commercial freezer loggers are fitted. Don't drill the cabinet — the insulation often has refrigerant lines running through it. ## The firmware The sketch reads both probes by index, reports the raw values for the chart, and separately maintains a rolling average that the alerts bind to. That split is the whole trick: raw data is what you want to look at, and a smoothed value is what you want to alarm on. ```cpp #include #include #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; const int ONE_WIRE_PIN = 4; const float ALPHA = 0.06; // ~15 min smoothing at a 60 s cadence OneWire oneWire(ONE_WIRE_PIN); DallasTemperature sensors(&oneWire); float freezerAvg = NAN, fridgeAvg = NAN; float smooth(float prev, float now) { return isnan(prev) ? now : prev + ALPHA * (now - prev); } void setup() { sensors.begin(); sensors.setResolution(12); Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } void loop() { Nodrix.run(); static unsigned long last = 0; if (millis() - last < 60000) return; last = millis(); sensors.requestTemperatures(); // 12-bit conversion, ~750 ms float freezer = sensors.getTempCByIndex(0); float fridge = sensors.getTempCByIndex(1); if (freezer > -100) { // -127 = probe not answering freezerAvg = smooth(freezerAvg, freezer); Nodrix.send("freezer_temp", freezer); Nodrix.send("freezer_temp_avg", freezerAvg); } if (fridge > -100) { fridgeAvg = smooth(fridgeAvg, fridge); Nodrix.send("fridge_temp", fridge); Nodrix.send("fridge_temp_avg", fridgeAvg); } } ``` The `> -100` guard matters. A DS18B20 that has lost its connection returns **-127°C**, and sending that value would look like the coldest freezer in history rather than a broken probe — skipping it leaves an honest gap in the chart instead. `getTempCByIndex` orders probes by their address, which is stable but arbitrary. Plug them in one at a time the first time and note which index is which, or read the addresses explicitly if you'd rather not depend on discovery order. ## Build the dashboard Put `freezer_temp` and `fridge_temp` on **chart** widgets. The raw traces are genuinely informative: you'll see the compressor's duty cycle as a regular sawtooth, and once you know its normal rhythm, a compressor that has stopped is obvious long before the temperature reaches anything alarming. Give each compartment a **value** widget bound to its averaged variable, so the number you glance at is the stable one. ## Add the alerts Create a **variable** trigger on `freezer_temp_avg`, condition *above -12*, with a [**Telegram** action](https://nodrix.live/guides/esp32-notifications). Bind it to the averaged variable, never the raw one — that's what makes a door left open for a minute a non-event while a genuine failure still gets through. Set the trigger's **cooldown** to a few hours. Without it, a freezer sitting just over the threshold re-fires the automation on every reading, and an alarm that messages you forty times is one you learn to ignore. Add a second automation on `fridge_temp_avg` above 8°C, and a **schedule** automation that reports both temperatures each morning. That daily message is your proof the chain still works — the board, the Wi-Fi, the automation, and the Telegram integration all had to be alive to produce it. ## Going further A door sensor turns a false positive into an explanation. A reed switch on a GPIO, [reported as a boolean](https://nodrix.live/guides/esp32-https-cloud), lets you see the door open in the same chart as the temperature bump — and lets an automation stay quiet when the two coincide. For a chest freezer in a garage or outbuilding, a water sensor on the same board is worth the couple of GPIOs it costs. A freezer that has failed and thawed announces itself on the floor, usually before anyone thinks to open the lid. If you're monitoring several units — a small kitchen, a lab, a shop — keep one board per unit rather than running long probe cables back to a central board. Cable runs are the fragile part of any 1-Wire installation, and one board per appliance means one failure never blinds you to everything. ## Notes A 12-bit conversion takes around 750 ms, during which `requestTemperatures()` blocks. At a one-minute cadence that's irrelevant; if you ever need faster sampling, drop to 10-bit resolution and the conversion time falls to roughly a quarter of that. The exponential smoothing constant is set for a 60-second reporting cadence. Change the cadence and the smoothing window changes with it — a smaller `ALPHA` means a longer, calmer average. The probes read the air, not the food. Air temperature swings much faster than a frozen mass does, which is why the smoothed value is the honest indicator of whether contents are actually at risk. ### FAQ **Q: How accurate is a DS18B20 at freezer temperatures?** Less accurate than its headline figure, and it's worth knowing before you trust it. The ±0.5°C spec applies from -10°C to +85°C only; below -10°C the datasheet tolerance widens to about ±2.0°C. For a freezer alarm that's fine, because you're detecting a ten-degree failure, not certifying a half-degree. For anything where the exact number matters — food safety records, lab samples — calibrate against a reference thermometer at the temperature you actually care about. **Q: How do I get the cable into the freezer without drilling?** Through the door gasket. A DS18B20 probe cable is thin enough that a modern magnetic seal closes over it without losing its grip, and it's what commercial freezer loggers do. Run it through the hinge side where the gasket compresses least, and check the door still pulls itself shut. Drilling the cabinet means going through the insulation and, on many units, the refrigerant lines buried in it. **Q: Won't opening the door set the alarm off?** It would if you alerted on raw readings, which is why this build doesn't. The sketch keeps a rolling average over roughly fifteen minutes and the alert binds to that, not to the instantaneous value. A door held open for a minute barely moves a fifteen-minute average; a compressor that has actually stopped moves it steadily and doesn't stop. **Q: Can one ESP32 watch both the fridge and the freezer?** Yes, and it's the reason to pick the DS18B20 over an analog sensor. It's a 1-Wire part with a unique 64-bit address burned into every unit, so several probes share a single data pin and the board tells them apart by address. Two probes, one GPIO, one board. **Q: What happens if the power goes out?** That's the case worth designing for, because a mains failure kills the freezer and the monitoring board at the same instant — and a board that is off cannot report anything. The fix is a small USB power bank between the board and the wall. The freezer stops, the ESP32 keeps running on battery, it watches the temperature climb, and the alert goes out over Wi-Fi or a phone hotspot while there's still time to act. --- ## Guide: ESP32 LoRa without LoRaWAN: long-range sensors to your own cloud URL: https://nodrix.live/guides/esp32-lora-gateway Category: project · Board: ESP32 Put a sensor kilometres from the nearest Wi-Fi using point-to-point LoRa and an ESP32 gateway — the duty-cycle limit that decides your whole design, honest range expectations, and why skipping LoRaWAN is usually the right call for a maker. Every other build on this site assumes Wi-Fi reaches the sensor. Sometimes it doesn't — a gate at the end of a field, a beehive in an orchard, a water tank on the far side of a property. LoRa exists for exactly that gap: kilometres of range, on batteries, for a few dollars of radio. This build puts a sensor somewhere with no network at all and gets its readings onto your dashboard, using an ESP32 as the bridge. It deliberately doesn't use LoRaWAN, and the first section explains why that's usually the right call. ## LoRa is not LoRaWAN The two get used interchangeably and they're different layers. **LoRa** is the modulation — the physical radio technique that trades data rate for range. **LoRaWAN** is a network protocol built on top: device join procedures, network servers, managed gateways, addressing, and encryption. Both use the same chips and the same over-the-air modulation. Everything separating them is software above the radio. LoRaWAN is the right answer when you want someone else's gateways to carry your traffic, or you're deploying hundreds of nodes across a city. It brings a network server you host or rent, a join procedure, and a payload format to decode. **Point-to-point LoRa** is two radios talking directly. No gateway infrastructure, no network server, no third party. You become responsible for what LoRaWAN would have handled — acknowledgements, retries, addressing between nodes, duty cycle discipline, and encryption — and for a handful of sensors reporting to one gateway you own, that's a much smaller job than running the alternative. ## The duty cycle decides your design This is the constraint that surprises people, and it's a legal limit rather than a technical one. The EU 868 MHz band operates under a **1% duty cycle** rule per ETSI EN 300 220. One percent of an hour is **36 seconds of transmission per hour** — roughly 864 seconds a day, and that's your entire budget. It applies to nodes and gateways alike. Now combine that with airtime. A LoRa packet's time on air depends heavily on spreading factor: at SF12, the slowest and longest-range setting, a single transmission takes around **1.5 seconds**. The arithmetic is unforgiving: **36 seconds ÷ 1.5 seconds ≈ 24 transmissions per hour.** At SF7 the same packet takes roughly a thirtieth of the airtime, so you could send far more — but SF7 reaches a fraction of the distance. That's the real trade: **range and message frequency are the same budget.** A sensor at the limit of its range gets to speak a couple of times an hour, and a design that ignores this is illegal rather than merely impolite. Regions differ. The US 915 MHz band uses frequency hopping with dwell-time rules instead of a flat duty cycle. Check what applies where you are — and note that **the hardware is band-specific**, so a 868 MHz module is not something you can reconfigure for 915 MHz. ## Range, honestly Datasheets quote up to 10 km for an SX1276 and up to 15 km for an SX1262. Those are line-of-sight figures with decent antennas and clear paths, and they're achievable — across a valley, over water, from a hilltop. Through buildings and trees, expect hundreds of metres to a couple of kilometres. Still a transformation compared to Wi-Fi, which stops at the end of the garden. Antenna placement and height matter more than anything you'll do in software; getting the gateway antenna up high beats every firmware optimisation available to you. Between the two chips, the **SX1262** is the better modern choice — more sensitivity, lower transmit current, and it's what current boards like recent Heltec modules carry. The SX1276 is older, common in cheaper modules, and perfectly serviceable. ## The architecture Since your nodes have no internet, the gateway supplies it: **Sensor node** (LoRa only, battery, in a field) **→ LoRa → ESP32 gateway** (LoRa + Wi-Fi, mains power, indoors) **→ HTTPS → your dashboard.** The gateway is an ordinary ESP32 build. It receives packets, and forwards their contents as normal telemetry. Nothing on the cloud side knows LoRa was involved — the readings look identical to a Wi-Fi sensor's, which means charts, automations, and alerts all work unchanged. ## What you'll need - **Two ESP32 boards with LoRa radios** — Heltec WiFi LoRa 32 or LilyGO T-Beam are the usual choices. - Both must be the **correct frequency variant for your region** (868 MHz in the EU, 915 MHz in the US). This is not configurable in software. - The **RadioLib** and **Nodrix** Arduino libraries. RadioLib handles SX1276 and SX1262 alike. - A **nodrix instance** with a project and a project token. ## The firmware Two sketches. The node reads a sensor and transmits a short string; the gateway listens and forwards. Keep payloads small — LoRa packets are tiny, and every byte costs airtime against your duty cycle. ```cpp // NODE: LoRa only, no Wi-Fi, battery powered #include SX1276 radio = new Module(18, 26, 14, 33); // CS, DIO0, RST, DIO1 — check your board void setup() { radio.begin(868.0); // MUST match your region and hardware radio.setSpreadingFactor(12); // max range, max airtime radio.setOutputPower(14); } void loop() { float moisture = analogRead(34) / 40.95; // 0-100% float volts = analogRead(35) * 2.0 * 3.3 / 4095.0; char msg[32]; snprintf(msg, sizeof(msg), "n1,%.1f,%.2f", moisture, volts); radio.transmit(msg); esp_deep_sleep(15ULL * 60 * 1000000); // 15 min: well inside the duty budget } // GATEWAY: LoRa in, HTTPS out #include #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; SX1276 radio = new Module(18, 26, 14, 33); void setup() { radio.begin(868.0); radio.setSpreadingFactor(12); // must match the node exactly Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } void loop() { Nodrix.run(); String packet; if (radio.receive(packet) != RADIOLIB_ERR_NONE) return; char node[8]; float moisture, volts; if (sscanf(packet.c_str(), "%7[^,],%f,%f", node, &moisture, &volts) != 3) return; Nodrix.send(String(node) + "_moisture", moisture); Nodrix.send(String(node) + "_battery", volts); Nodrix.send(String(node) + "_rssi", radio.getRSSI()); Nodrix.send(String(node) + "_snr", radio.getSNR()); } ``` Prefixing variables with a node id is what makes this scale past one sensor. Add a second node sending `n2,...` and it creates its own variables automatically — no gateway changes, no registration. Sending **RSSI and SNR** as variables is the detail that pays for itself. They're your link quality, charted over time, and they tell you whether a node that went quiet has a flat battery or a deteriorating radio path. Debugging a link kilometres away without them is guesswork. The `sscanf` check discards malformed packets. On an open ISM band you will receive noise and fragments from other devices, and a gateway that trusts everything it hears will publish garbage into your dashboard. ## Build the dashboard Because each node names its own variables, a second sensor appears on the dashboard on its first transmission without the gateway changing. Put each node's reading on a **chart** widget and its battery voltage on another — a remote node's battery curve is the difference between swapping a cell during a planned visit and walking to a field to find out why something went quiet. Give `rssi` and `snr` a chart of their own. This is the payoff for sending them, and it's link diagnostics you cannot get any other way at this range: rain, foliage filling in over a summer, and an antenna nudged by wind all show up as a slow decline weeks before packets actually stop arriving. A node that falls silent with healthy RSSI history is a flat battery; one that fades first is a path problem. For alerts, a **variable** trigger on a node's battery voltage gives you replacement warnings, and a **schedule** automation reporting each node once a day is what distinguishes "nothing has changed" from "the gateway died on Tuesday". Routing those anywhere useful is the [notifications guide](https://nodrix.live/guides/esp32-notifications). ## Going further Adding nodes costs nothing on the gateway — a third sensor sending `n3,...` creates its own variables and appears on its own. What it does cost is airtime, which is shared: every node draws from the same regional budget, so a growing network means longer intervals rather than more traffic. Sending commands *back* to a node is possible and deliberately awkward. The node has to be listening to receive, which contradicts the deep sleep that makes its battery last, and the gateway's transmit budget is the same 1% everyone else's is. The usual pattern is a node that wakes, transmits, and briefly listens for a reply before sleeping — accepting that a command waits until the node's next scheduled wake rather than arriving when you press the button. The gateway is an ordinary ESP32 with spare pins, so it can carry its own sensors too. Its readings travel over [Wi-Fi and HTTPS](https://nodrix.live/guides/esp32-https-cloud) rather than the radio, which means they cost no airtime at all and arrive at whatever cadence you like. ## Notes Spreading factor, bandwidth, and frequency must match exactly on both ends. A mismatch isn't a degraded link — it's silence, which reads as broken hardware and is the most common reason a first LoRa build never receives anything. Point-to-point LoRa has no encryption. Anyone with a receiver on your frequency reads your payload as plainly as your gateway does. For soil moisture, shrug. For anything revealing occupancy or security state, add AES to the payload before you transmit. The duty cycle applies to your gateway too, which matters the moment you want it to acknowledge or send commands downward. A gateway serving several nodes can exhaust its transmit budget quickly, and this is the main reason downlink on LoRa is rationed rather than casual. [Deep sleep](https://nodrix.live/guides/esp32-deep-sleep-battery) suits nodes perfectly here. The node above wakes every fifteen minutes, transmits for about a second and a half, and sleeps — a duty cycle low enough that battery life is measured in seasons, and comfortably inside what the regulations allow. ### FAQ **Q: What's the difference between LoRa and LoRaWAN?** LoRa is the radio modulation — the physical layer. LoRaWAN is a network protocol stacked on top of it, with join procedures, network servers, and managed gateways. They use the same chips and the same modulation; everything that differs is software above the radio. For a handful of your own sensors reporting to your own gateway, point-to-point LoRa skips an entire infrastructure layer you'd otherwise have to run or rent. **Q: How often can I actually transmit?** Far less often than you'd expect, and this is a legal limit rather than a technical one. The EU 868 MHz band allows a 1% duty cycle under ETSI EN 300 220 — about 36 seconds of transmission per hour, total. At SF12, a single packet takes roughly 1.5 seconds of airtime, which works out to around 24 transmissions per hour. Design your reporting interval from that budget, not from what your sensor could produce. **Q: How far will it really reach?** Kilometres in clear line of sight, and dramatically less through buildings. The headline figures — 10 km for an SX1276, 15 km for an SX1262 — are open-air numbers with good antennas and clear sightlines. In a town, expect hundreds of metres to a couple of kilometres. It's still transformative compared to Wi-Fi, which gives up at the end of the garden. **Q: Is LoRa data encrypted?** Not in point-to-point mode. LoRaWAN specifies encryption; raw LoRa is a radio link and sends exactly what you hand it, readable by anyone with a receiver on your frequency. If your payload is a soil moisture reading, that may not matter. If it's a door state or anything that reveals occupancy, encrypt it yourself with AES before transmitting — the developer owns security on this path. **Q: Can nodrix receive LoRaWAN directly?** Not today — there's no LoRaWAN integration, so a Things Network setup would need a small shim to reshape TTN's uplink format into a telemetry POST. The gateway approach in this guide avoids that entirely: your ESP32 gateway speaks LoRa on one side and plain HTTPS on the other, so the platform only ever sees ordinary telemetry. Native LoRaWAN support is on the roadmap. --- ## Guide: ESP32 MicroPython to the cloud — a live dashboard with no broker URL: https://nodrix.live/guides/esp32-micropython-cloud Category: hardware · Board: ESP32 Push ESP32 sensor data to a cloud dashboard with MicroPython over HTTPS — no MQTT broker, no SDK. Full code for telemetry, commands back to the board, the internal temperature sensor, and an honest account of what TLS costs you on an ESP32. Most ESP32 cloud tutorials hand you C++. That's a fine default, but it's not why a lot of people bought the board — MicroPython turns an ESP32 into something you can poke at over a serial prompt, change a line, and rerun, without a compile-and-flash cycle between every idea. This guide builds the whole loop in MicroPython: Wi-Fi, telemetry up to a cloud dashboard over plain HTTPS, commands back down to the board, and deep sleep. No MQTT broker, no vendor SDK, and no external sensor required to start — the ESP32's own die sensor is enough to prove the pipeline before you wire anything. ## What you'll build A script that connects to Wi-Fi, posts readings to your own instance, checks for pending commands, applies them, and sleeps. Variables show up on the dashboard the first time they're seen, so there's nothing to register in advance. ## Why MicroPython here (and when Arduino still wins) The honest case for MicroPython on an ESP32 is iteration speed. You get a REPL over USB, so you can read a sensor interactively and see the value immediately instead of adding a `Serial.println` and waiting forty seconds. For sensor-and-report projects — which is most IoT — that loop is genuinely faster to work in. The honest case against it is equally clear. Compiled C++ wins on tight timing, on RAM headroom, and on library coverage: if your part has a driver, it's an Arduino library first and a MicroPython module maybe. Anything with microsecond deadlines — bit-banged protocols, high-rate sampling, interrupt-driven counting — should be C++. And as the section below covers, TLS is more comfortable on the Arduino side. Pick MicroPython because you want the REPL and the shorter loop, not because you expect it to be smaller or faster. It isn't. ## What you'll need - An **ESP32** dev board — any common variant. The internal temperature sensor differs between the original ESP32 and the C3/C6/S2/S3, and the script handles both. - A **USB cable** and **esptool** to flash the firmware. - A **nodrix instance** with a project and a project token. - No sensor, no wiring, no breadboard to get the first reading on screen. ## Flashing MicroPython Download the current `.bin` for your exact chip from [micropython.org/download](https://micropython.org/download/) — the ESP32, C3, C6, S2, and S3 each have their own build, and flashing the wrong one gives you a board that boots to nothing. Then erase and write: `esptool.py --chip esp32 --port /dev/ttyUSB0 erase_flash`, then `esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x1000 ESP32_GENERIC.bin`. Erasing first matters more than it looks: leftover filesystem blocks from a previous firmware are a common cause of a board that boots into a broken state. Once it's flashed, install the HTTP client on the device with `mpremote mip install requests`. ## The script One file. It connects, reads the die sensor, posts telemetry, drains any pending control writes, acks them, and loops. The `mcu_temperature` / `raw_temperature` split is handled at import time so the same script runs on an original ESP32 and on a C3 or S3 without edits. ```python import network, time, gc, json, machine, esp32 import requests SSID = "your-ssid" PASS = "your-password" HOST = "https://nodrix.you.workers.dev" TOKEN = "tok_your_project_token" HEADERS = {"Authorization": "Bearer " + TOKEN, "Content-Type": "application/json"} def read_die_temp_c(): if hasattr(esp32, "mcu_temperature"): return esp32.mcu_temperature() # C3 / C6 / S2 / S3 — Celsius return (esp32.raw_temperature() - 32) / 1.8 # original ESP32 — Fahrenheit def connect_wifi(timeout=20): wlan = network.WLAN(network.STA_IF) wlan.active(True) if not wlan.isconnected(): wlan.connect(SSID, PASS) deadline = time.time() + timeout while not wlan.isconnected(): if time.time() > deadline: machine.reset() machine.idle() return wlan def post(path, payload): gc.collect() r = None try: r = requests.post(HOST + path, headers=HEADERS, data=json.dumps(payload)) return r.status_code finally: if r: r.close() def send_telemetry(metrics): return post("/v1/telemetry", {"metrics": metrics}) def apply_control(): gc.collect() r = None try: r = requests.get(HOST + "/v1/control", headers=HEADERS) if r.status_code != 200: return pending = r.json().get("control", []) finally: if r: r.close() done = [] for w in pending: if w["variable"] == "led": machine.Pin(2, machine.Pin.OUT).value(1 if w["value"] in (True, "on", 1) else 0) done.append(w["id"]) if done: post("/v1/control/ack", {"ids": done}) connect_wifi() while True: send_telemetry({"temperature": read_die_temp_c()}) apply_control() gc.collect() time.sleep(5) ``` Telemetry returns `204` on success. Values can be numbers, strings, booleans, or null, and every key becomes a variable — send `{"temperature": 24.1, "humidity": 61}` and you get two. ## Build the dashboard Open your project and the `temperature` variable is already listed. Drop a **value** widget on it for the current reading, or a **chart** widget to watch it move. For the downlink, add a **toggle** bound to a variable named `led` — flipping it queues a write that the script picks up on its next poll and applies to GPIO2, the onboard LED on most DevKit boards. That round trip is the thing worth confirming early: it proves both directions work before you've soldered anything. ## The TLS reality on ESP32 This is the section most MicroPython tutorials skip, and it's the one that will cost you an evening. HTTPS works on the ESP32 port — mbedtls is built in, and `requests.post` to an `https://` URL needs no extra setup. The catch is memory. A TLS session wants a large record buffer, and MicroPython is already holding a heap full of Python objects. The result is a failure mode that looks like flakiness rather than a bug: the same script that ran fine for an hour throws an mbedtls handshake error, usually after the heap has fragmented. Three habits avoid nearly all of it, and they're already in the script above: - **Collect before every request.** `gc.collect()` immediately before a TLS call gives the handshake the largest contiguous block available. This single line fixes most reports of intermittent handshake failures. - **Close every response, always.** A leaked socket is the most common reason a long-running script stops sending after a few hours. The `try/finally` shape above means an exception mid-request still closes it. - **Don't hold JSON you've finished with.** Parse what you need, let the response object go, and collect. Large response bodies kept in scope are what fragment the heap in the first place. If you're on a memory-tight board and still fighting it, that's a real signal to use the Arduino path instead — [the same build in C++](https://nodrix.live/guides/esp32-https-cloud) hands the socket and the TLS session to a library that keeps one connection open rather than handshaking repeatedly. ## Going further Swap the die sensor for a real one and nothing else changes. A BME280 over I2C gives you temperature, humidity, and pressure as three keys in the same `send_telemetry` call — the [weather station build](https://nodrix.live/guides/esp32-weather-station) covers the sensor side, and the reporting line is identical. For a battery device, replace the `while True` loop with a wake-report-sleep structure: `machine.deepsleep(300000)` at the end of the script sleeps five minutes and reboots into it. Read the die sensor immediately after waking — the chip is coolest then, so the reading is closest to ambient. The [battery-life guide](https://nodrix.live/guides/esp32-deep-sleep-battery) covers the power budget and the dev-board traps that quietly ruin it. ## Notes The internal sensor measures the die, not the room. It reads several degrees above ambient because the chip heats itself, and that offset grows the longer the board has been awake — which is exactly why it's a good pipeline test and a poor thermometer. `requests` and `urequests` are the same library under two names. New code should import `requests`; older tutorials import `urequests`, which still resolves through a compatibility wrapper. The control endpoint returns pending writes and expects an ack by id. Skipping the ack isn't fatal — the platform simply keeps offering the same write until someone confirms it — but it does mean your board reapplies the same command on every poll. ### FAQ **Q: Can MicroPython on an ESP32 do HTTPS?** Yes, and it's the part worth planning for. The ESP32 port ships mbedtls, so `requests.post` to an https:// URL works out of the box. What bites people is memory: a TLS session wants a large record buffer, and on a board with a few tens of KB of free heap a handshake can fail with an mbedtls error even though the same code ran yesterday. Collect garbage before the request, close every response, and keep one connection pattern rather than opening sockets ad hoc. **Q: Should I use urequests or requests?** Write new code against `requests` — that's the current name in micropython-lib, installed with `mip install requests`. `urequests` is the legacy name and still works through a compatibility wrapper, which is why so much older tutorial code imports it. Both are the same library; only the import line differs. **Q: Is MicroPython fast enough for a sensor project?** For anything that reads a sensor and reports it, comfortably. MicroPython is interpreted, so it's slower than compiled C++ per operation, but a project that samples every few seconds spends essentially all its time waiting on Wi-Fi and the sensor, not executing Python. Where it genuinely loses is tight timing — bit-banged protocols, high-rate sampling, and interrupt work with microsecond deadlines belong in the Arduino framework or ESP-IDF. **Q: Does the ESP32 have a built-in temperature sensor I can use?** It does, and the API depends on which chip you have. The original ESP32 exposes `esp32.raw_temperature()`, which returns Fahrenheit. The C3, C6, S2, and S3 expose `esp32.mcu_temperature()`, which returns Celsius. Either way it measures the die, not the room — it reads high because the chip warms itself, so treat it as a free way to prove your pipeline works, not as a thermometer. **Q: How does the board receive commands without a broker?** It asks. The board polls a control endpoint, applies any pending variable writes it finds, and acks them by id so the platform stops resending. That's the whole downlink — no broker, no subscription, no always-on socket. Polling every few seconds feels immediate for switching things on and off, and for a battery device you poll once per wake instead. --- ## Guide: ESP32 OTA updates: ship firmware to deployed boards without touching them URL: https://nodrix.live/guides/esp32-ota-updates Category: concept · Board: ESP32 How to update ESP32 firmware over the air properly: the partition trade nobody warns you about, an HTTPS pull triggered from your own dashboard, rollback that catches a bad build, and fleet versions you can actually see. The first ESP32 you deploy is easy to update: unplug it, carry it to your desk, flash it. The fourth one is in a roof space. The seventh is potted in resin on a tank. At some point "just reflash it" stops being an answer, and the project either grows an update path or quietly freezes at whatever firmware it happened to have. This guide covers doing that properly: what OTA costs you in flash, how to pull an update over HTTPS, how rollback saves you from a bad build, and how to see which board is running what. ## The trade nobody mentions first OTA works by keeping **two complete copies of your firmware** on the board. The running copy stays untouched while the new one downloads into a second slot; only when the download completes and verifies does a pointer flip and the board reboot into the new image. That's precisely what makes it safe — lose power halfway through and the working copy is still there. The cost is arithmetic. A 4MB ESP32 that could hold one 3MB application holds two of about 1.3MB instead. This produces the single most common OTA mistake, and it happens by accident. A sketch grows, the compiler complains it doesn't fit, and the obvious fix in the Arduino IDE is switching the partition scheme to **Huge APP (3MB No OTA)**. It compiles, it works, and OTA is now impossible — that scheme has no OTA partitions at all. If you're running out of room and want to keep updates, **Minimal SPIFFS** is the scheme you want: a much larger app slot, OTA intact. Decide this at the start of a project, not when the board is already on a roof. ## How the boot slot is chosen Worth understanding, because it explains rollback. Alongside the two app slots is a small `otadata` partition holding a counter that points at whichever slot should boot. Updating doesn't rewrite your firmware in place — it writes a new image to the inactive slot and then updates that pointer. That partition is deliberately two flash sectors, written and verified independently, so that losing power while updating the pointer itself can't leave the board unbootable. If the two disagree on the next boot, a counter decides which was written more recently. ## Rollback: the part that makes this survivable An update that downloads perfectly and then crashes on boot is worse than no update at all, because now the board is unreachable *and* broken. The bootloader can handle this, if you let it. With rollback enabled, a newly flashed image boots in a **pending verification** state. It has to declare itself healthy by calling `esp_ota_mark_app_valid_cancel_rollback()`. If it crashes, hangs, or reboots before making that call, the bootloader gives up on it and boots the previous slot instead. Where you put that call is the entire design decision. Calling it at the top of `setup()` means "working" only means "reached the first line of code" — which a build with a broken Wi-Fi config passes easily, and then sits there bricked-but-happy forever. Call it **after the board has connected and reported in**, so confirmation means the firmware can actually do its job. Note that rollback needs a partition table with two app slots and **no factory partition** — the OTA-capable schemes are already laid out this way. ## The update flow Nodrix doesn't host firmware binaries; it isn't a build artefact store. What it does host is the signal and the visibility, which is most of what a small fleet needs: - The board reports `firmware_version` as [ordinary telemetry](https://nodrix.live/guides/esp32-https-cloud), so the dashboard shows what every device is running. - You publish the new binary anywhere static — R2, a GitHub release, any bucket over HTTPS. - A control write to a `firmware_url` variable tells a board to go and fetch it. The rollout is then just a dashboard action, and because the version is a variable, you can watch the fleet move across as devices pick it up. ## The firmware The write handler receives the URL, checks it against a host you trust, and hands off to `httpUpdate`. The confirmation call sits at the end of a successful startup, not the beginning. ```cpp #include #include #include #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; const char* FW_VERSION = "1.4.0"; const char* TRUSTED_FW = "https://fw.example.com/"; // updates must start with this NODRIX_WRITE("firmware_url") { String url = value.asString(); if (!url.startsWith(TRUSTED_FW)) { Nodrix.send("ota_status", "rejected_host"); return; } Nodrix.send("ota_status", "downloading"); Nodrix.flush(); // get it out before the radio is busy WiFiClientSecure client; client.setInsecure(); // pin a CA in production httpUpdate.rebootOnUpdate(true); t_httpUpdate_return r = httpUpdate.update(client, url); if (r == HTTP_UPDATE_FAILED) { Nodrix.send("ota_status", httpUpdate.getLastErrorString()); } // On success the board reboots inside update() and never reaches here. } void setup() { Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); unsigned long t0 = millis(); while (!Nodrix.connected() && millis() - t0 < 30000) { Nodrix.run(); delay(50); } if (Nodrix.connected()) { Nodrix.send("firmware_version", FW_VERSION); Nodrix.send("ota_status", "ok"); Nodrix.flush(); esp_ota_mark_app_valid_cancel_rollback(); // only now is this build "good" } } void loop() { Nodrix.run(); } ``` The `Nodrix.flush()` before the download matters. Once `httpUpdate` starts, the board is busy writing flash and will reboot without warning, so anything you wanted to report needs to have actually left first. The host check is the security boundary. Without it, anyone who can write that variable can point your board at any binary on the internet — the toggle isn't the risk, a device that installs firmware from arbitrary addresses is. ## Watching the rollout Put `firmware_version` on a dashboard widget and it becomes your fleet inventory. With several boards in one project, use a distinct variable per device — `sensor_a_version`, `sensor_b_version` — and a glance tells you which ones took the update and which are stuck. `ota_status` is the other half. Because it reports `downloading`, `rejected_host`, or a specific error string, a failed update tells you *why* rather than leaving you with a board that simply went quiet. Add a **variable** trigger on `ota_status` not equal to `ok` with a [Telegram action](https://nodrix.live/guides/esp32-notifications), and a failed rollout tells you rather than waiting to be discovered. ## Notes `setInsecure()` keeps the example readable. Firmware delivery is exactly the wrong place to skip certificate validation in production — an unauthenticated transport plus an unvalidated binary is how a fleet gets taken over. Pin the CA, and sign your images if the devices matter. [Battery devices](https://nodrix.live/guides/esp32-deep-sleep-battery) that spend their lives asleep need the update check on wake, not on a persistent socket. Poll the control endpoint once per wake cycle, and expect rollouts to take as long as your sleep interval. An OTA download needs enough free heap for the TLS session on top of everything your sketch is already holding. If updates fail on a memory-tight build while plain telemetry works, that's the cause — free what you can before starting one. Test rollback deliberately before you rely on it. Flash a build that connects and then panics, and confirm the board comes back on the previous version. Finding out that rollback was misconfigured during a real bad deploy defeats the point of having it. ### FAQ **Q: Why does enabling OTA halve my available program space?** Because the board has to hold two complete copies of your firmware. An OTA update downloads the new build into an inactive slot while the current one keeps running, then flips a pointer and reboots into it. That's what makes the update safe — a failure mid-download leaves the working copy untouched — and it's why a 4MB ESP32 gives you roughly 1.3MB per slot instead of one big 3MB one. **Q: I picked Huge APP because my sketch got too big. Can I still do OTA?** No, and this catches people constantly. The Huge APP partition scheme is 3MB of application with no OTA partitions at all, so the option isn't merely inconvenient, it's absent. If your sketch has outgrown the default scheme, Minimal SPIFFS gives you a much larger app slot while keeping OTA. Choosing Huge APP is a decision to flash by cable forever. **Q: What happens if the update downloads but the new firmware is broken?** With rollback enabled, the board recovers on its own. A freshly-flashed image boots in a pending state and must call `esp_ota_mark_app_valid_cancel_rollback()` to confirm itself; if it crashes or reboots before doing so, the bootloader reverts to the previous slot. Put that call after your Wi-Fi connects rather than at the top of setup, so 'working' means it can actually reach the network. **Q: Is it safe to trigger updates from a dashboard?** It is when the device decides what to trust. The dashboard write should be a signal, not a command — the board fetches over HTTPS from a host you control and refuses URLs that don't match it. The danger isn't someone flipping a toggle, it's a device that will install firmware from any address it's handed. **Q: Can nodrix host my firmware binaries?** Not today — it stores telemetry, not build artefacts. What it does well is the orchestration around the update: devices report their running version as a variable so you can see your fleet's versions at a glance, and a control write tells a board to go and fetch. Host the binary anywhere static — R2, GitHub releases, any bucket — and let the dashboard drive the rollout. --- ## Guide: ESP32-S3 edge AI: send the answer, not the image URL: https://nodrix.live/guides/esp32-s3-edge-ai Category: project · Board: ESP32-S3 A person-detecting ESP32-S3 camera that never uploads an image: on-device inference at roughly 200 ms, and the vector speedup that isn't automatic. Most "AI camera" projects are not doing AI on the camera. They stream frames to a server, the server runs a model, and the board is a webcam with extra steps. That works, and it costs you bandwidth forever, a round trip of latency per decision, and the privacy of every frame. The ESP32-S3 can genuinely run the model itself. This build detects a person in frame on-device and sends a single boolean to a dashboard — the image never leaves the board. ## Why the S3 specifically The S3 is the variant with dual Xtensa LX7 cores at 240 MHz, 512 KB of internal SRAM, support for octal PSRAM, and — the part that matters here — **SIMD vector instructions built into the CPU cores**. The classic ESP32 has no vector hardware at all. The gap that opens is large. Benchmarks put the S3 at roughly **4.5× the original ESP32 on 16-bit detection models**, and a person-detection model at 96×96 input runs in about **200 ms** per frame. Five frames a second, deciding locally, on a board that costs a few dollars. ## The speedup is not automatic Here is the thing that isn't in the marketing, and it will quietly cost you the entire advantage. **The compiler does not emit those vector instructions.** They're reachable through hand-written assembly, not through the optimiser noticing your loops. You get the acceleration by using libraries that were built to exploit them — Espressif's **ESP-DL**, and the **esp-nn** kernels that back their TensorFlow Lite Micro port. Write your own straightforward inference loop in C, or pull in a generic TFLite build that isn't using the accelerated kernels, and an S3 performs like a classic ESP32 with a bigger price tag. Use Espressif's `esp-tflite-micro` rather than a generic port, and check that the accelerated kernels are actually enabled. ## What's realistic, honestly An S3 is not a small GPU. What fits is narrow and well-defined: - **Is there a person in frame?** The canonical example, ~250 KB model, ~200 KB arena. - **Was a wake word spoken?** Audio models are small and this is a mature use case. - **Does this vibration signature match a known fault?** Sensor classification is cheap compared to vision. - **Which of a few gestures was that?** What doesn't fit is general object recognition across many categories, anything at high resolution, or anything you'd describe as "understanding" a scene. The constraint is productive: one question answered reliably beats many answered badly. ## The architecture: send conclusions, not frames This is the design point, and it's the reason edge AI matters beyond being clever. A 96×96 grayscale frame is about 9 KB. Streaming a few of those per second is a continuous upload, forever, so that something elsewhere can decide what the board already determined locally. The inference result — `person_detected: true` — is a handful of bytes, sent only when it changes. So the board runs the model and reports the answer. Bandwidth collapses to nothing, the decision has no round trip, and no image ever leaves the room. Your dashboard stores what happened, not what it looked like. ## What you'll need - An **ESP32-S3 board with PSRAM and a camera** — an ESP32-S3-EYE, XIAO ESP32S3 Sense, or a Freenove S3 camera board. - **PSRAM enabled** in your build settings. This is not optional here. - Espressif's **esp-tflite-micro**, the **esp32-camera** driver, and the **Nodrix** library. - A **nodrix instance** with a project and a project token. ## The firmware The loop is: grab a frame, run it through the interpreter, and report only when the answer changes. Camera pin mappings differ between boards, so take those from your board's own camera example rather than from here — getting them wrong is the usual reason `esp_camera_init` fails. ```cpp #include #include #include #include #include #include "person_detect_model_data.h" const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; constexpr int kArenaSize = 200 * 1024; static uint8_t* tensor_arena = nullptr; tflite::MicroInterpreter* interpreter = nullptr; TfLiteTensor* input = nullptr; bool lastState = false; void setupModel() { tensor_arena = (uint8_t*)heap_caps_malloc(kArenaSize, MALLOC_CAP_SPIRAM); const tflite::Model* model = tflite::GetModel(g_person_detect_model_data); static tflite::MicroMutableOpResolver<5> resolver; resolver.AddConv2D(); resolver.AddDepthwiseConv2D(); resolver.AddAveragePool2D(); resolver.AddReshape(); resolver.AddSoftmax(); static tflite::MicroInterpreter it(model, resolver, tensor_arena, kArenaSize); interpreter = ⁢ interpreter->AllocateTensors(); input = interpreter->input(0); } void setup() { camera_config_t config = { /* board-specific pins */ }; config.pixel_format = PIXFORMAT_GRAYSCALE; config.frame_size = FRAMESIZE_96X96; config.fb_location = CAMERA_FB_IN_PSRAM; esp_camera_init(&config); setupModel(); Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } void loop() { Nodrix.run(); camera_fb_t* fb = esp_camera_fb_get(); if (!fb) return; for (size_t i = 0; i < fb->len && i < (size_t)input->bytes; i++) { input->data.int8[i] = (int8_t)(fb->buf[i] - 128); // uint8 -> int8 quantised } esp_camera_fb_return(fb); // return it immediately unsigned long t0 = millis(); if (interpreter->Invoke() != kTfLiteOk) return; unsigned long ms = millis() - t0; TfLiteTensor* out = interpreter->output(0); bool person = out->data.int8[1] > out->data.int8[0]; if (person != lastState) { // report changes only lastState = person; Nodrix.send("person_detected", person); Nodrix.send("inference_ms", (int)ms); Nodrix.event(person ? "person_arrived" : "person_left"); } } ``` Three lines are doing quiet work. `MALLOC_CAP_SPIRAM` puts the tensor arena in PSRAM rather than competing with Wi-Fi and TLS for internal SRAM. Returning the frame buffer immediately after copying matters because the camera driver only holds a small number of them and forgetting to return one stalls capture within seconds. And the `- 128` converts the camera's unsigned bytes into the signed range the quantised model expects — skip it and inference runs happily and returns nonsense. Reporting `inference_ms` costs nothing and tells you whether the accelerated kernels are actually in use. If that number sits near 200 you're getting the S3's vector path; if it's closer to a second, you're not. ## Build the dashboard `person_detected` on a **value** widget answers the question. The more interesting widget is a **chart** of the same variable over time — because the board only reports transitions, that trace is an occupancy log, and it costs a few bytes per event rather than a video stream. `inference_ms` on a **value** widget is your health check for the model path. The sketch also emits `person_arrived` and `person_left` events, which **event** triggers can hang automations off — lights, notifications, a webhook — without polling anything. ## Going further Swap the model and the same structure holds. Espressif's speech recognition stack gives you wake-word detection with no camera at all, and audio models are far smaller than vision ones — a good first edge-AI project if you don't have a camera board, and one path among the three in the [voice control guide](https://nodrix.live/guides/voice-control-esp32). For a custom classifier, Edge Impulse is the shortest path from your own recorded data to a deployed, quantised model, and it targets the S3 directly. Training on your own sensor data is where this gets genuinely useful: a [vibration classifier](https://nodrix.live/guides/esp32-vibration-monitor) trained on *your* machine beats a generic model easily. Pairing this with [Claude over MCP](https://nodrix.live/guides/control-esp32-with-claude-mcp) closes an interesting loop — a board that decides locally and a language model that can query those decisions and act on them, with neither one streaming video anywhere. ## Notes Quantisation is what makes any of this fit. These models run in 8-bit integers rather than floats, which is both what shrinks them to a couple hundred kilobytes and what the vector instructions accelerate. A float model of the same architecture will not fit and would not be fast if it did. The tensor arena size is found by experiment. Too small and `AllocateTensors` fails; oversized and you're wasting PSRAM you may want for frame buffers. Start at the model's documented figure and trim. PSRAM is slower than internal SRAM. Putting the arena there is the right call because it's large, but performance-critical scratch buffers are better left internal — which is the balancing act every TinyML build on this chip ends up doing. ### FAQ **Q: How much faster is the ESP32-S3 than a classic ESP32 for this?** Around 4.5× on 16-bit detection models, which is the difference between a demo and something usable. The S3's Xtensa LX7 cores carry SIMD vector instructions that the original ESP32 simply doesn't have, and neural network inference is exactly the workload they were added for. A person-detection model at 96×96 lands near 200 ms per frame on an S3. **Q: Do I get that speedup automatically by using an S3?** No, and this catches people out. The compiler does not emit those vector instructions — they're reachable through hand-written assembly, which in practice means through libraries built to use them, like Espressif's ESP-DL and the esp-nn kernels behind their TensorFlow Lite port. Write your own naive inference loop in plain C on an S3 and you'll get classic-ESP32 performance on faster silicon. **Q: Do I need PSRAM?** For anything involving a camera, yes. The model itself wants a couple hundred kilobytes of tensor arena, the framework wants its own, and a camera frame buffer sits on top of that — against 512 KB of internal SRAM that also has to hold Wi-Fi and TLS buffers. Boards sold for AI work pair the S3 with 8 MB of PSRAM for this reason, and a board without it will run out of memory in ways that look like random crashes. **Q: Can I send the camera image to my dashboard?** Not as telemetry, and it's worth understanding why that's the right design rather than a limitation. Telemetry carries numbers, strings, and booleans — the conclusions a device reaches. Streaming frames to a cloud is the architecture edge AI exists to replace: you'd be paying bandwidth continuously to send images so that something else can decide what this board already knows. **Q: What can an ESP32-S3 realistically recognise?** Narrow, well-defined things: is there a person in frame, was a wake word spoken, does this vibration signature match a fault, which of a handful of gestures was that. It is not a general vision system, and models that classify hundreds of categories won't fit. The constraint is genuinely productive — a sensor that reliably answers one question is more useful than one that answers many badly. --- ## Guide: Build an ESP32 solar and battery monitor that counts real amp-hours URL: https://nodrix.live/guides/esp32-solar-battery-monitor Category: project · Board: ESP32 A complete ESP32 solar battery monitor on the INA226: measure charge and discharge current through a proper shunt, count amp-hours in and out, and watch a live dashboard from anywhere — with an honest account of why battery voltage is not state of charge. An off-grid battery bank fails slowly and then all at once. The useful monitor isn't the voltage readout on the charge controller — it's a record of how much energy actually went in and came out, kept somewhere you can look at it from the house instead of the shed. This build measures battery voltage and bidirectional current with an INA226, counts amp-hours in and out, and streams the lot to a live dashboard. It's aimed at 12V and 24V systems: a cabin, a shed, an RV, a boat, a solar-powered gate. ## What you'll build A single current sensor on the battery leg reporting five variables: bus voltage, signed current, instantaneous power, and cumulative amp-hours charged and discharged. It's the DC counterpart to [metering mains power](https://nodrix.live/guides/esp32-energy-meter), and the measurement problem is genuinely different. Positive current means the panels are outrunning the loads; negative means they aren't. ## Why the INA226 — and the shunt problem nobody mentions The INA226 is the right chip here. It's a high-side current and power monitor with a 16-bit ADC and a 0–36V bus range, and it holds accuracy down to microamps. The INA219 you'll see in older tutorials has a 12-bit ADC and a 26V ceiling, and it drifts badly at low current — which is precisely the overnight trickle and dawn charge you care about on a solar system. Now the part that catches almost everyone. The INA226 doesn't measure current directly; it measures the voltage across a shunt resistor, and its shunt input range is **±81.92 mV**. The breakout board you'll buy ships with a **0.1 Ω** shunt soldered to it. Ohm's law does the rest: **81.92 mV ÷ 0.1 Ω ≈ 0.82 A.** That is the entire measurement range of a stock INA226 module. It is a bench instrument as sold, and wiring it into a solar system either reads a permanently pinned value or releases the smoke. To measure a real system you desolder that 0.1 Ω part and fit a low-value power shunt: **0.002 Ω** gives about 20 A, which is why the Arduino library's calibration call defaults to exactly `20.0` amps and `0.002` ohms. Size the shunt for the largest current your system can actually produce, not the largest it usually does. ## What you'll need - An **ESP32** dev board — any common DevKit variant. - An **INA226** breakout, plus a **0.002 Ω** (or similar) power shunt to replace the stock one. - A **12V or 24V** battery bank. Not 48V — see the bus-voltage limit above. - An appropriately fused connection into the battery's negative or positive leg. - The **Nodrix** and **RobTillaart/INA226** Arduino libraries. - A **nodrix instance** with a project and a project token. ## Wiring The INA226 sits between the battery and everything else, with the shunt carrying full system current. The ESP32 only ever sees I2C: | From | To | Wire | |------|----|------| | INA226 VCC | ESP32 3V3 | Power | | INA226 GND | ESP32 GND | Ground | | INA226 SDA | ESP32 GPIO21 | I2C data | | INA226 SCL | ESP32 GPIO22 | I2C clock | | INA226 VIN+ | Battery side of shunt | Sense high | | INA226 VIN− | Load side of shunt | Sense low | The shunt goes in the battery leg so the sensor sees net battery current — charge minus load. Fuse that leg. A shunt is a deliberate low-resistance path across your battery terminals, and the failure mode of getting it wrong is not a wrong reading. The module's I2C address is set by its A0 and A1 pads, defaulting to `0x40`. That matters if you later add a second sensor, which the last section covers. ## The firmware Calibration is mandatory — `getCurrent()` and `getPower()` return nothing meaningful until `setMaxCurrentShunt()` has told the chip what shunt is fitted. Pass your actual shunt value here, not the stock one. Amp-hours come from integrating current over time. The sketch samples every two seconds, accumulates amp-seconds, and reports the running totals as two separate counters so a glance tells you the day's balance. ```cpp #include #include #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; const float SHUNT_OHM = 0.002; // the shunt you actually fitted const float MAX_AMP = 20.0; INA226 ina(0x40); double ahIn = 0, ahOut = 0; // cumulative, amp-hours unsigned long lastSample = 0; void setup() { Wire.begin(); ina.begin(); ina.setMaxCurrentShunt(MAX_AMP, SHUNT_OHM); Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); lastSample = millis(); } void loop() { Nodrix.run(); unsigned long now = millis(); if (now - lastSample >= 2000) { double hours = (now - lastSample) / 3600000.0; lastSample = now; float amps = ina.getCurrent(); // signed: + charging, - discharging if (amps >= 0) ahIn += amps * hours; else ahOut += -amps * hours; } static unsigned long lastReport = 0; if (millis() - lastReport >= 30000) { lastReport = millis(); Nodrix.send("battery_voltage", ina.getBusVoltage()); Nodrix.send("battery_current", ina.getCurrent()); Nodrix.send("battery_power", ina.getPower()); Nodrix.send("ah_charged", (float)ahIn); Nodrix.send("ah_discharged", (float)ahOut); } } ``` Sampling and reporting run on separate clocks on purpose. Amp-hour counting needs frequent samples to be accurate — a two-second cadence catches load steps that a thirty-second one would average away — while the dashboard only needs a reading every half minute. ## Build the dashboard Put `battery_voltage` and `battery_current` on **chart** widgets side by side. Read together they tell the story a single number can't: voltage sagging while current goes sharply negative is a load switching on, and voltage climbing with positive current is the array doing its job. `battery_power` suits a **value** widget with a **gauge** if you want the at-a-glance version, and the two amp-hour counters belong on plain **value** widgets — they're running totals, and their usefulness is in the difference between them. ## Add the alerts Two automations earn their keep here. A **variable** trigger on `battery_voltage` below 11.8V (for a 12V lead-acid bank) with a [**Telegram** action](https://nodrix.live/guides/esp32-notifications) catches deep discharge while you can still do something about it. A second on `battery_current` above your array's realistic maximum catches a sensor or wiring fault, because a reading that can't physically happen is worth knowing about immediately. Add a **schedule** automation that reports the day's amp-hours each evening. A bank that quietly stops charging is the failure you most want to catch, and only a positive daily message tells you the difference between a good day and a dead board. ## Voltage is not state of charge This is where most DIY battery monitors mislead their owners, so it's worth being plain about. A battery's terminal voltage moves with current, not just with charge. Under load it sags; while charging it rises. A 12V lead-acid bank reading 12.1V might be half empty at rest or nearly full with a heavy load on it, and the voltage alone can't tell you which. Resting voltage genuinely does indicate state of charge — but only once the battery has been idle long enough to settle, and that equilibrium takes hours, not minutes. Counting amp-hours is the better instrument, and it's why this build integrates current rather than just logging voltage. Energy in and energy out is a direct measurement of what happened, not an inference from a number that three different conditions can produce. It isn't perfect either. Coulomb counting drifts, because charging a battery never returns quite as much energy as it took, and small errors in current accumulate over days. The standard fix is to reset the counters whenever the bank reaches a known full charge — an automation on `battery_voltage` holding at absorption voltage is a reasonable trigger for exactly that. ## Going further Two INA226s give you the full picture. Set the second module's address to `0x41` with its A0 pad, put it on the array leg, and you can separate generation from consumption instead of inferring the split from net current. The sketch grows by one object and two `Nodrix.send` lines. For a remote installation without Wi-Fi at the battery, the wake-report-sleep pattern from the [battery-life guide](https://nodrix.live/guides/esp32-deep-sleep-battery) works — with one caveat specific to this build. Amp-hour counting needs continuous sampling, so a sleeping board can't do it. Either keep the monitor powered from the bank it's watching (its draw is negligible against a solar system) or accept voltage-and-power snapshots without the counters. ## Notes The INA226 reports signed current, so sign is your charge/discharge indicator and no extra hardware is needed to detect direction. If yours reads negative while charging, VIN+ and VIN− are swapped. Shunt resistors have a temperature coefficient, and a shunt carrying 20A gets warm. Precision readings from a hot shunt drift slightly; for amp-hour counting over a day this is lost in the noise, and it's another reason not to undersize the part. The 0.1 Ω shunt you desolder is worth keeping. It turns the module back into a useful bench instrument for anything under 800 mA, which is most of what you'll want to measure on a workbench. ### FAQ **Q: Why doesn't the INA226 module measure my solar current?** Because of the shunt it ships with. Almost every INA226 breakout carries a 0.1 Ω shunt, and with the chip's ±81.92 mV shunt range that caps measurement at about 0.82 A — fine for bench work, useless for a solar system. You have to replace it with a much lower value: 0.002 Ω gives you roughly 20 A, which is why the common Arduino library defaults to exactly those numbers. **Q: Will this work on a 48V system?** No. The INA226's bus voltage input tops out at 36V, which comfortably covers 12V and 24V banks and rules out 48V. For a 48V system you need a part rated for it or a front-end divider, and a divider costs you the accuracy that made the INA226 worth choosing. **Q: Should I use an INA219 instead?** Only if you already own one. The INA226 has a 16-bit ADC against the INA219's 12-bit and reads to 36V against 26V, and the difference shows up exactly where a solar build lives — the small charge currents at dawn and dusk. Reports of the INA219 drifting by double-digit percentages at low overnight current are common; the INA226 stays honest down to microamps. **Q: How do I know the state of charge?** Not from voltage, which is the thing most DIY monitors get wrong. A battery's voltage sags under load and rises while charging, so a reading taken during either tells you about the current, not the charge. Resting voltage is meaningful but needs the battery genuinely idle, and even then takes time to settle. Counting amp-hours in and out — which this build does — tracks charge far better between full-charge resets. **Q: Where in the circuit does the shunt go?** On the battery leg, not the panel leg. A shunt there measures net battery current, and because the INA226 reports a signed value you get charging and discharging from one sensor: positive means the panels are winning, negative means the loads are. Putting it on the panel side only tells you what's coming in, which is the less useful half. --- ## Guide: Build an ESP32 vibration monitor for motor health, measured properly URL: https://nodrix.live/guides/esp32-vibration-monitor Category: project · Board: ESP32 A complete ESP32 vibration sensor for predictive maintenance: sample an ADXL345 over SPI, convert acceleration to the RMS velocity that ISO 10816 actually grades machines on, and trend a motor's health on a live dashboard instead of guessing from raw g. Rotating machines announce their failures for weeks before they happen. A pump, a fan, a compressor, a lathe spindle — imbalance, misalignment, and worn bearings all show up as rising vibration long before anything sounds wrong. Industrial condition monitoring is built entirely on noticing that rise, and the instruments that do it cost more than the motors most people own. This build does the useful part with an ESP32 and a ten-dollar accelerometer: it measures vibration the way the standards define it, and trends it so you can see a machine getting worse. ## The thing most DIY vibration projects get wrong Search for an ESP32 vibration monitor and you'll mostly find sketches that read an accelerometer and report raw g, or a "vibration level" derived from how much the numbers wobble. That's a number that goes up when things get worse, which feels like enough. It isn't, for two reasons. **Machine vibration standards are written in velocity.** ISO 10816, and the ISO 20816 series that now supersedes it, grade severity as broadband **RMS velocity in mm/s** across the **10–1000 Hz** band. Every published threshold, every A/B/C/D zone, every alarm limit a maintenance engineer would recognise is in those units. Report raw acceleration and none of that reference material applies to your numbers. **Acceleration weights the spectrum wrongly.** Acceleration scales with frequency squared, so a raw-g reading is dominated by high-frequency content while the low-frequency imbalance that actually chews through bearings barely registers. Velocity flattens that out, which is exactly why the standards chose it. Converting is the difference between a graph that wiggles and a measurement that means something. ## What you'll build An ESP32 sampling an ADXL345 at 3200 Hz over SPI, computing RMS velocity in mm/s from a windowed spectrum, and reporting it every minute alongside the dominant frequency. The dashboard trends it; an automation alerts when the trend crosses a level you set from your own baseline. ## What you'll need - An **ESP32** dev board — any common DevKit variant. - An **ADXL345** breakout, wired for **SPI** (see below — this is not optional). - A rigid mount: a threaded stud, or a strong magnet on a clean machined surface. - The **Nodrix** and **arduinoFFT** Arduino libraries. - A **nodrix instance** with a project and a project token. ## Wiring The ADXL345 supports both I2C and SPI, and most tutorials use I2C because it's two wires. Use SPI anyway: the I2C bus cannot sustain the sample rate this measurement needs, and an under-sampled signal doesn't give you a slightly worse answer, it gives you a wrong one through aliasing. | From | To | Wire | |------|----|------| | ADXL345 VCC | ESP32 3V3 | Power | | ADXL345 GND | ESP32 GND | Ground | | ADXL345 SCL/SCLK | ESP32 GPIO18 | SPI clock | | ADXL345 SDA/MOSI | ESP32 GPIO23 | SPI MOSI | | ADXL345 SDO/MISO | ESP32 GPIO19 | SPI MISO | | ADXL345 CS | ESP32 GPIO5 | Chip select | Mount the sensor on the bearing housing, not on a guard or a cover panel. Radial to the shaft catches imbalance and misalignment best. Whatever you choose, record it and repeat it exactly — a trend built from readings taken at different mounting points is not a trend. ## The firmware The sketch samples one axis at a fixed rate, removes the DC component (gravity, which would otherwise dominate and make the integration diverge), runs an FFT, and converts each frequency bin from acceleration to velocity by dividing by 2πf. Summing the velocity bins across 10–1000 Hz gives the broadband RMS velocity the standards are written in. ```cpp #include #include #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; const int CS_PIN = 5; const int N = 1024; const float FS = 3200.0; // ADXL345 max over SPI const float LSB_TO_MS2 = 0.0039 * 9.80665; // full-res: 3.9 mg/LSB double vReal[N], vImag[N]; ArduinoFFT FFT(vReal, vImag, N, FS); void wr(uint8_t reg, uint8_t val) { digitalWrite(CS_PIN, LOW); SPI.transfer(reg); SPI.transfer(val); digitalWrite(CS_PIN, HIGH); } int16_t readZ() { digitalWrite(CS_PIN, LOW); SPI.transfer(0x36 | 0x80); // DATAZ0, read uint8_t lo = SPI.transfer(0), hi = SPI.transfer(0); digitalWrite(CS_PIN, HIGH); return (int16_t)((hi << 8) | lo); } void setup() { pinMode(CS_PIN, OUTPUT); digitalWrite(CS_PIN, HIGH); SPI.begin(); SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE3)); wr(0x31, 0x0B); // full resolution, +/-16g wr(0x2C, 0x0F); // 3200 Hz output data rate wr(0x2D, 0x08); // measure Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } void loop() { Nodrix.run(); static unsigned long last = 0; if (millis() - last < 60000) return; last = millis(); unsigned long period = 1000000UL / (unsigned long)FS; double mean = 0; for (int i = 0; i < N; i++) { unsigned long t = micros(); vReal[i] = readZ() * LSB_TO_MS2; vImag[i] = 0; mean += vReal[i]; while (micros() - t < period) { } } mean /= N; for (int i = 0; i < N; i++) vReal[i] -= mean; // strip gravity / DC FFT.windowing(FFTWindow::Hann, FFTDirection::Forward); FFT.compute(FFTDirection::Forward); FFT.complexToMagnitude(); double sumSq = 0, peak = 0; int peakBin = 0; double binHz = FS / N; for (int i = 1; i < N / 2; i++) { double f = i * binHz; if (f < 10 || f > 1000) continue; // the ISO band double aAmp = 2.0 * vReal[i] / N; // m/s^2, amplitude double vAmp = aAmp / (2.0 * PI * f) * 1000; // -> mm/s sumSq += vAmp * vAmp / 2.0; // amplitude -> RMS if (vAmp > peak) { peak = vAmp; peakBin = i; } } Nodrix.send("vibration_mm_s", (float)sqrt(sumSq)); Nodrix.send("dominant_hz", (float)(peakBin * binHz)); } ``` Two lines carry most of the meaning. Subtracting the mean removes the steady 1g of gravity sitting on whichever axis faces down — leave it in and the division by frequency turns it into an enormous phantom low-frequency velocity. And `aAmp / (2πf)` is the integration: in the frequency domain, integrating acceleration to velocity is just dividing each bin by its angular frequency, which is far more stable than integrating in the time domain where drift accumulates. ## Build the dashboard `vibration_mm_s` on a **chart** widget is the whole point of the build. A single reading tells you little; a month of readings tells you everything. Healthy machines are boringly flat, and the shape you're watching for is a slow upward drift. Put `dominant_hz` on a **value** widget. Compared against the machine's running speed it's a first diagnostic hint: energy concentrated at one times running speed suggests imbalance, twice running speed often points at misalignment or looseness. It won't diagnose a bearing, but it narrows what to look at. ## Add the alert Set a **variable** trigger on `vibration_mm_s` with a threshold drawn from your own baseline — run the machine healthy for a week, read the chart, and set the alarm meaningfully above the normal band, routed wherever you'll actually see it via the [notifications guide](https://nodrix.live/guides/esp32-notifications). Give it a generous **cooldown**; a developing fault is a slow story, and you don't need hourly reminders. The ISO figures are the sanity check rather than the trigger. Small machines sit below roughly 0.71 mm/s when newly commissioned, with about 4.5 mm/s as the boundary into genuinely unacceptable for that class. If your healthy baseline already sits near the top of that range, that's worth investigating before you trust the trend at all. ## Going further Sampling all three axes triples the data and roughly triples the useful information, because imbalance, misalignment, and looseness present differently across radial and axial directions. The FFT work is per-axis; the loop structure doesn't change. Pairing vibration with motor current is where small-scale condition monitoring gets genuinely good. The [energy meter build](https://nodrix.live/guides/esp32-energy-meter) reports the electrical side, and rising vibration alongside rising current is a much stronger signal than either alone — mechanical drag shows up in both. Classifying the signature rather than just trending it is the next step up, and it runs on the board: the [S3 edge AI build](https://nodrix.live/guides/esp32-s3-edge-ai) covers training a model on your own sensor data and reporting the conclusion instead of the spectrum. If you outgrow the ADXL345, the upgrade is a lower-noise accelerometer with more bandwidth rather than a faster processor. The ESP32 is not the limiting factor here; the sensor's noise floor is. ## Notes The busy-wait timing loop is crude but adequate at 3200 Hz, and it keeps the sample interval even — which matters more than absolute rate, because a jittery sample clock smears the spectrum. A hardware-timer implementation is the correct upgrade if you push the rate higher. A Hann window is applied before the transform to reduce spectral leakage. Without it, a frequency that doesn't land exactly on a bin centre bleeds across neighbours and inflates the broadband sum. The 10 Hz lower bound is part of the standard, not an arbitrary filter setting. Below it, sensor noise and thermal drift dominate, and dividing those by a very small frequency produces impressive velocity numbers that mean nothing at all. ### FAQ **Q: Why measure velocity instead of acceleration?** Because that's what machine vibration standards are written in. ISO 10816 and its successor ISO 20816 grade severity as broadband RMS velocity in mm/s over the 10–1000 Hz band, and the reason is physical: velocity correlates with the energy a machine is dumping into its bearings across a wide frequency range, while raw acceleration over-weights high frequencies and under-weights the low-frequency imbalance that actually destroys machines. An accelerometer measures acceleration, so converting is part of the job, not an optional refinement. **Q: Can an ADXL345 really do this?** For broadband trending, yes — with one condition: use SPI, not I2C. The part samples at up to 3200 Hz over SPI, which after Nyquist covers the 10–1000 Hz band the standard specifies. Over I2C the bus itself becomes the bottleneck and you can't sustain the rate. What the ADXL345 won't do well is high-frequency bearing defect analysis, which needs a lower-noise sensor and more bandwidth. **Q: What vibration level should worry me?** For a small machine under about 15 kW, ISO 10816 puts newly commissioned equipment below 0.71 mm/s and the alarm boundary around 4.5 mm/s, with medium machines allowed proportionally more. Treat those as orientation rather than verdict — the number depends on how the sensor is mounted, so your own established baseline is a better alarm source than the table. A motor that has been running at 1.2 mm/s for a year and is now at 2.5 mm/s is telling you something, regardless of which zone either value falls in. **Q: Does it matter how I mount the sensor?** Enormously — mounting is the single biggest source of bad vibration data. The sensor needs a rigid path to the bearing housing. A stud or a strong magnet on a machined flat is good; double-sided tape is mediocre; a 3D-printed bracket or a zip tie is not measuring the machine, it's measuring the bracket's own resonance. Mount at the bearing, in line with the shaft or radial to it, and mount the same way every time or your trend is meaningless. **Q: Can this detect a specific bearing fault?** Not reliably, and it's worth being straight about that. Identifying which bearing element is failing means resolving defect frequencies well above this setup's usable band with a lower-noise accelerometer than the ADXL345. What this build does well is notice that something is getting worse and when — which is what actually gets a machine inspected before it fails. --- ## Guide: Build an ESP32 water tank level monitor with low-water alerts URL: https://nodrix.live/guides/esp32-water-tank-monitor Category: project · Board: ESP32 A complete ESP32 water level sensor for a tank or sump: measure depth with a waterproof JSN-SR04T ultrasonic probe, stream percentage full to a live dashboard, and get a Telegram alert before the tank runs dry — no float switches, no broker, on your own Cloudflare account. Running a tank dry is the kind of problem you only notice at the worst possible moment. A level sensor fixes it for about ten dollars, and the useful version isn't a gauge you walk out to read — it's a number on your phone that tells you how full the tank is and messages you before it matters. This build measures depth with a waterproof ultrasonic probe, streams percentage full to a live dashboard, and sends a Telegram alert when the level drops past a threshold you can change without touching the board. No float switches to corrode, no MQTT broker, and nothing running on a server at home. ## What you'll build An ESP32 mounted at the top of a tank, reporting two variables: the measured distance to the water surface and the derived percentage full. The dashboard shows a gauge and a history chart; an automation watches the percentage and fires the alert. ## Why the JSN-SR04T, and not an HC-SR04 The HC-SR04 is the ultrasonic sensor every tutorial reaches for, and it is the wrong part here. Its two transducers are open to the air. The space above stored water sits at essentially 100% humidity, so those transducers collect condensation, and a fogged transducer doesn't fail cleanly — it returns plausible-looking wrong numbers. The JSN-SR04T solves the packaging problem rather than the physics. The transducer is potted into a sealed probe on the end of a cable, with the driver board outside the tank. The probe tolerates condensation and the odd splash, and the electronics stay dry. It buys that robustness with one significant trade, covered below: a much longer minimum range. ## What you'll need - An **ESP32** dev board — any common DevKit variant. - A **JSN-SR04T** waterproof ultrasonic sensor (version 2.0 if you can pick, for reasons below). - A stable **5V supply** for the sensor — this matters more than it sounds. - A weatherproof enclosure and a way to mount the probe pointing straight down. - A **nodrix instance** with a project and a project token. ## Wiring Four connections. The sensor's driver board takes 5V, the probe plugs into it, and two GPIOs do the timing: | From | To | Wire | |------|----|------| | JSN-SR04T VCC | ESP32 VIN / 5V | Power | | JSN-SR04T GND | ESP32 GND | Ground | | JSN-SR04T TRIG | ESP32 GPIO5 | Trigger pulse | | JSN-SR04T ECHO | ESP32 GPIO18 | Echo timing | Two wiring notes worth getting right the first time. The sensor wants a genuinely stable 5V — brownouts on a shared USB rail are the most common cause of a sensor that returns one frozen value forever. And on **version 2.0** boards the logic runs down to 3.0V, so ECHO connects straight to a 3.3V GPIO; on **older revisions** ECHO idles at 5V and wants a divider (a 1kΩ / 2kΩ pair to ground) to keep it off the ESP32's pin. If you don't know which revision you have, fit the divider — it's harmless on a 2.0. ## Mounting: the 20 cm rule This is the constraint that decides whether the build works, and most tank tutorials inherit HC-SR04 numbers and never mention it. The sealed probe has a **minimum detection distance of about 20 cm**. Closer than that, the echo returns while the transducer is still ringing, and the sensor reports a wrong number rather than an error. So the probe must sit at least 20 cm above the highest the water will ever reach — not 20 cm above the tank lid, 20 cm above the *full* waterline. Get this wrong and the failure is maddening to debug, because it only appears when the tank is full: the monitor reads correctly all week, then reports nonsense on the day it rains. Mount high, and if the tank fills close to its lid, accept that the top 20 cm is a blind spot and calibrate `FULL_CM` to the first distance the sensor reads reliably. Point the probe straight down at open water, away from the inlet stream and at least a hand's width from the tank wall, so the beam isn't clipping the side on its way down. ## The firmware The sketch pulses the trigger, times the echo, and converts the round trip to a distance. It takes five readings and uses the **median** — ultrasonic sensors report the nearest thing that echoes, and a single ripple or a bit of floating debris will otherwise show up as a sudden empty tank. Calibration is two constants. `EMPTY_CM` is the distance the sensor reads with the tank empty; `FULL_CM` is what it reads when full. Measure both rather than calculating them from the tank's dimensions — what matters is where the probe actually ended up, not where you meant to put it. ```cpp #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; const int TRIG_PIN = 5; const int ECHO_PIN = 18; const float EMPTY_CM = 180.0; // sensor -> tank floor, measured const float FULL_CM = 35.0; // sensor -> full waterline, measured (keep >= 20) float readDistanceCm() { digitalWrite(TRIG_PIN, LOW); delayMicroseconds(4); digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10); digitalWrite(TRIG_PIN, LOW); unsigned long us = pulseIn(ECHO_PIN, HIGH, 60000UL); // timeout ~10 m if (us == 0) return -1; // no echo return us / 58.0; } float medianDistanceCm() { float s[5]; int n = 0; for (int i = 0; i < 5; i++) { float d = readDistanceCm(); if (d > 0) s[n++] = d; delay(60); } if (n == 0) return -1; for (int i = 1; i < n; i++) { // insertion sort, n is 5 float k = s[i]; int j = i - 1; while (j >= 0 && s[j] > k) { s[j + 1] = s[j]; j--; } s[j + 1] = k; } return s[n / 2]; } void setup() { pinMode(TRIG_PIN, OUTPUT); pinMode(ECHO_PIN, INPUT); Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } void loop() { Nodrix.run(); static unsigned long last = 0; if (millis() - last >= 60000) { last = millis(); float cm = medianDistanceCm(); if (cm < 0) return; // skip, don't send garbage float pct = (EMPTY_CM - cm) / (EMPTY_CM - FULL_CM) * 100.0; pct = constrain(pct, 0.0, 100.0); Nodrix.send("tank_distance_cm", cm); Nodrix.send("tank_level", pct); } } ``` Note what happens on a failed reading: the sketch returns without sending. A gap in the chart is an honest signal that the sensor didn't answer. Sending a zero instead would look exactly like an empty tank, and would fire your low-water alert at three in the morning. ## Build the dashboard Both variables appear the first time they're seen. Drop a **gauge** widget on `tank_level` with a range of 0–100 for the at-a-glance view, and a **chart** widget on the same variable for history — the slope is the genuinely useful part, because it tells you consumption rate and therefore how many days you have left. Keep `tank_distance_cm` on a **value** widget too. When something looks wrong, the raw distance is what you debug with; the percentage is derived and hides the problem. ## Add the low-water alert Create an automation with a **variable** trigger on `tank_level`, condition *below 20*, and a **Telegram** action — or any of the other channels in the [notifications guide](https://nodrix.live/guides/esp32-notifications). The message can interpolate the value, so it arrives as something useful rather than a bare ping. Putting the threshold in the cloud rather than in the sketch is the point. A tank monitor ends up somewhere inconvenient — a roof, a pump house, a crawlspace — and the moment you want to change 20% to 15%, the difference between editing an automation and fetching a laptop and a USB cable is the difference between doing it and not bothering. Add a second automation with a **schedule** trigger that reports the level once a day. A tank that has stopped reporting entirely is the failure you most want to hear about, and only a positive daily message tells you the difference between "level is fine" and "the board died a week ago". ## Going further To drive a pump, add a relay and a write handler bound to a `pump` variable: `NODRIX_WRITE("pump") { digitalWrite(PUMP_PIN, value.asBool() ? HIGH : LOW); Nodrix.send("pump", value.asBool()); }` Then let an automation start it when the level drops and stop it when the level recovers — the [same closed-loop pattern](https://nodrix.live/guides/esp32-automatic-plant-watering) as a self-watering planter, at tank scale. Echo the real pin state back with `Nodrix.send` as shown, so a dashboard opened later hydrates with what the hardware is actually doing rather than what someone last clicked. Give any pump automation a hard maximum runtime as a second stop condition. Level-based control assumes the level reading is correct, and a sensor that fails while a pump is running is precisely the scenario that empties a well or floods a floor. For a battery build on a remote tank, drop the always-on socket and use the wake-report-sleep pattern from the [battery-life guide](https://nodrix.live/guides/esp32-deep-sleep-battery). A tank level changes slowly enough that one reading every fifteen minutes is plenty, and the sensor's 5V draw only exists during the brief window it's awake. ## Notes Ultrasonic time-of-flight varies with air temperature — sound travels roughly 0.6% faster per degree Celsius. Across a tank's working range that's a centimetre or two, which is irrelevant for a percentage-full reading and would matter if you were billing someone for the contents. The `pulseIn` timeout of 60,000 µs caps a measurement at roughly ten metres. A tank deeper than that needs a longer timeout; a shallower one gets faster failure detection from a shorter one. Foam and heavy surface debris absorb ultrasound rather than reflecting it, which shows up as intermittent no-echo readings rather than wrong ones. The median filter and the skip-on-failure behaviour above handle occasional cases; persistent foam is a reason to reach for a pressure sensor instead. ### FAQ **Q: Why use a JSN-SR04T instead of a cheap HC-SR04?** Because the air above stored water is saturated, and an HC-SR04's exposed transducers fog up and then read nonsense. The JSN-SR04T puts the transducer in a sealed, potted probe on a cable, so the electronics stay outside the tank and the sensing face tolerates condensation and splashing. It's a few dollars more and it's the difference between a build that works in week three and one that doesn't. **Q: How far above the water does the sensor need to be?** At least 20 cm above the highest the water will ever reach. The JSN-SR04T's sealed probe has a minimum detection distance of about 20 cm — far longer than the HC-SR04's 2 cm — and anything closer than that reads as garbage rather than as zero. This is the single most common reason a tank monitor works fine when half full and goes haywire when it fills. **Q: Can I use this to control a pump automatically?** Yes, and the guide wires it up — but put the cutoff logic in the cloud, not in the sketch. A level threshold that starts and stops a pump is an automation you can retune from your phone without reflashing a board that may be bolted to a tank. Add a hard maximum runtime as a second condition so a bad reading can never run a pump indefinitely. **Q: Why are my readings jumping around?** Ultrasonic sensors report the nearest thing that echoes, which in a tank can be a ripple from the inlet, floating debris, or the tank wall if the beam clips it. Take several readings and use the median rather than the average — one wild value poisons a mean but not a median — and mount the probe away from where water enters. **Q: Is a pressure sensor more accurate?** A submersible pressure transducer is more accurate and immune to surface chop, because it measures the column of water above it rather than bouncing sound off the top of it. It also costs several times more and has to live in the water permanently. For a domestic tank where you want to know roughly how many days you have left, ultrasonic from above is the better trade. --- ## Guide: ESP32 Wi-Fi provisioning: stop hardcoding credentials in your sketch URL: https://nodrix.live/guides/esp32-wifi-provisioning Category: concept · Board: ESP32 How to get Wi-Fi credentials onto an ESP32 without recompiling: a captive portal that also collects your server and token, credentials stored in NVS, a factory reset that actually works, and an honest look at what the provisioning window exposes. Every guide on this site, including every one I've written, starts the same way: `const char* WIFI_SSID = "your-ssid";` That's the right call for a tutorial and the wrong call for anything you keep. It means moving a board to a different network requires a laptop and a cable, it puts your Wi-Fi password in your source code, and it makes the project impossible to hand to anyone else without them editing and recompiling it. Provisioning fixes all three. The board asks for credentials once, remembers them, and one firmware image works everywhere — which is also the precondition for [updating those boards over the air](https://nodrix.live/guides/esp32-ota-updates) instead of visiting them. ## What you'll build An ESP32 that boots, tries its saved network, and — if it has none or can't reach it — starts its own access point with a setup page. That page collects the Wi-Fi network **and** your instance host and project token. Everything is saved, and a button press wipes it. ## The three approaches, honestly **Captive portal.** The board becomes an access point; you connect a phone, a setup page appears, you pick a network and type the password. It's the most common approach because it needs no app and no special tooling, and it's what this guide uses. **Native Espressif provisioning.** ESP-IDF ships a provisioning system over BLE or SoftAP with a proof-of-possession secret and encrypted exchange, driven by Espressif's phone apps. It's genuinely more secure and the right answer for a product. It's heavier to set up and ties your users to a specific app, which is a poor trade for a handful of personal devices. **Improv Wi-Fi.** A small open standard for provisioning over serial or BLE, which lets a web page configure a board over Web Serial. Lovely when the device is plugged into a computer during setup; not applicable once it's on a roof. For a maker fleet, the captive portal wins on the thing that matters most: no app, no cable, works from any phone. ## What you'll need - An **ESP32** dev board. - The **WiFiManager** and **Nodrix** Arduino libraries. - A spare GPIO and a momentary button, for the factory reset. - A **nodrix instance** with a project and a project token. ## The firmware The flow is: check the reset button, hand Wi-Fi to the portal, then start Nodrix with whatever host and token the portal collected. The integration is cleaner than it looks. `Nodrix.begin(host, token)` — the overload without Wi-Fi arguments, alongside [the usual four-argument form](https://nodrix.live/guides/esp32-https-cloud) — checks whether Wi-Fi is already connected and returns immediately if it is. So once WiFiManager has done its job, Nodrix simply uses the connection that already exists. ```cpp #include #include #include const int RESET_PIN = 0; // BOOT button on most DevKits Preferences prefs; WiFiManager wm; char host[64] = "nodrix.you.workers.dev"; char token[64] = ""; bool shouldSave = false; void setup() { pinMode(RESET_PIN, INPUT_PULLUP); prefs.begin("nodrix", false); prefs.getString("host", host, sizeof(host)); prefs.getString("token", token, sizeof(token)); if (digitalRead(RESET_PIN) == LOW) { // held at boot -> forget everything delay(3000); if (digitalRead(RESET_PIN) == LOW) { prefs.clear(); wm.resetSettings(); ESP.restart(); } } WiFiManagerParameter pHost("host", "nodrix host", host, sizeof(host)); WiFiManagerParameter pToken("token", "project token", token, sizeof(token)); wm.addParameter(&pHost); wm.addParameter(&pToken); wm.setSaveConfigCallback([]() { shouldSave = true; }); wm.setBreakAfterConfig(true); // callback fires even if Wi-Fi failed wm.setConfigPortalTimeout(300); // don't sit in the portal forever if (!wm.autoConnect("nodrix-setup")) { ESP.restart(); // timed out; try the saved network again } if (shouldSave) { strncpy(host, pHost.getValue(), sizeof(host)); strncpy(token, pToken.getValue(), sizeof(token)); prefs.putString("host", host); prefs.putString("token", token); } // Wi-Fi is already up, so this only sets up the socket. Nodrix.begin(host, token); // Let the library reconnect on its own if the link drops later. Nodrix.addAP(WiFi.SSID().c_str(), WiFi.psk().c_str()); } void loop() { Nodrix.run(); // ... your sketch } ``` That last `addAP` line is easy to miss and worth understanding. Nodrix reconnects dropped links through its own multi-AP handler, which only knows about networks you've given it. WiFiManager connects the board without telling Nodrix anything, so handing back the SSID and passphrase after the fact is what lets the library recover from a Wi-Fi drop hours later. `setBreakAfterConfig(true)` is the other non-obvious line. Without it, the save callback never fires if the Wi-Fi attempt fails — so a user who typos their password loses the host and token they just typed as well. ## The factory reset is not optional A device that cannot forget its network is a device you cannot move. That's the whole problem provisioning was meant to solve, so a build without a reset path has only relocated the cable dependency rather than removed it. Holding the BOOT button for three seconds at power-up is a good pattern: no extra hardware on most dev boards, hard to trigger accidentally, and it clears both the Wi-Fi credentials and your stored host and token together. Clearing only one leaves a board that connects to a network and then talks to the wrong instance, which is a confusing state to debug. ## What the setup window exposes Two things worth being clear about, because most captive-portal tutorials skip them. **The portal AP is open by default, and the page is plain HTTP.** For the minute or two the board is in setup mode, anyone in range can connect to it. In practice the exposure is small — it's a brief window, and you're the only client — but provision at home rather than in a public space, and set a password on the portal if devices will be configured somewhere less controlled. **NVS is not encrypted by default.** Credentials saved to the ESP32's storage are readable by anyone who can attach a flash programmer to the board. That's an acceptable trade for a home sensor and not for something that leaves your control; NVS encryption exists for the latter case. ## Notes Reflashing your sketch does not clear saved credentials. NVS lives in its own partition and survives application uploads, which is exactly what you want in the field and confusing on the bench — a board that keeps joining an old network is remembering, not malfunctioning. Erase flash entirely, or use the reset button. Give the config portal a timeout. A board that fails to connect once and then sits in setup mode forever will never retry the network that was only briefly down, which is the most common way a provisioned device becomes unreachable. Name the setup AP after the device rather than the project when you deploy several — worth deciding early if you're building out [several boards at once](https://nodrix.live/guides/esp32-project-ideas). Three boards all advertising `nodrix-setup` is a guessing game; `tank-sensor-setup` is not. ### FAQ **Q: What's actually wrong with putting the SSID in the sketch?** Three things, and they get worse as a project succeeds. Moving a device to a different network means recompiling and reflashing it. Your home Wi-Fi password ends up in your source, which is awkward the moment you publish the sketch or push it to a repo. And you can't hand the device to anyone else — a build that only works on your network isn't a project someone can follow, it's a project they have to modify. **Q: Where do the credentials get stored?** In NVS, the ESP32's non-volatile storage partition, which survives reboots and reflashing the application. That persistence is the point, and it's also the thing that surprises people: erasing and reuploading your sketch does not clear saved Wi-Fi credentials, so a device that keeps connecting to an old network is usually remembering rather than misbehaving. **Q: Is the setup portal secure?** Adequately, with caveats worth knowing. The configuration access point is typically open, and the portal is plain HTTP, so during that window someone in range could connect. It's a short window on a network with one client, so the practical risk is low — but provision on your own premises rather than in a café, and put a password on the portal AP if the device will be set up somewhere public. **Q: How do I make a device forget its network?** Give it a button. Read a GPIO at boot and, if it's held, call the library's reset and restart into the portal. This is the difference between a prototype and something usable: without it, the only way to move a device to a new network is a cable and a laptop, which is exactly the problem provisioning was supposed to solve. **Q: Can I provision the server and token this way too?** Yes, and you should — Wi-Fi is only half of what a cloud-connected board needs to know. Captive portal libraries support custom fields, so the same form that collects the network can collect your instance host and project token. Then one firmware image works for every device you deploy, and each one is configured rather than compiled. --- ## Guide: Matter and Thread for makers: what they solve, and what they leave to you URL: https://nodrix.live/guides/matter-thread-for-makers Category: concept · Board: ESP32-C6 An honest guide to Matter and Thread for DIY hardware: which ESP32 variants can do what, why a border router may not be needed at all, the certification reality for homemade devices, and the jobs Matter deliberately doesn't do. Matter arrived promising to end the smart-home compatibility mess, and for buying off-the-shelf devices it has largely delivered. For makers building their own hardware, the picture is more interesting and less discussed: some of what you'd expect to be hard is easy, and some of what sounds like a detail turns out to be the whole decision. Here's what Matter and Thread actually do for a DIY project, and — more usefully — what they don't. ## Matter is not Thread These two get conflated constantly, and separating them clears up most of the confusion. **Matter** is an application-layer standard: how a device describes itself, what a "light" or a "temperature sensor" is, how it's commissioned, and how it's controlled. It runs over **Wi-Fi, Ethernet, or Thread**. **Thread** is one of those transports — a low-power 802.15.4 mesh network, the same radio family as Zigbee. The practical consequence is the thing most people get wrong: **you do not need a Thread border router to build a Matter device.** A Matter device over Wi-Fi joins Apple Home, Google Home, or Alexa with no extra hardware. If you own a classic ESP32 and want a Matter-compatible relay, nothing is stopping you today. Thread earns its complexity for battery devices and for coverage. A Thread sensor sips power in a way a Wi-Fi device can't, and Thread meshes so distant nodes route through nearer ones. That needs a border router to bridge the mesh onto your IP network — many smart speakers and hubs already contain one. ## Which ESP32 can do what The split is a hardware fact, not a firmware option. **Thread-capable — the C6, H2, and C5** carry an 802.15.4 radio. These can be Thread devices. The [C6](https://nodrix.live/guides/esp32-c6-for-makers) is the most interesting of the three because it has Wi-Fi 6 *and* 802.15.4 on one chip, which means it can also act as a **Thread border router itself**, bridging the mesh to your network from a single five-dollar part. **Wi-Fi Matter only — the classic ESP32, S3, and C3** have no 802.15.4 radio. They can be perfectly good Matter devices over Wi-Fi. They can never be Thread devices. The **H2** is the odd one: 802.15.4 and Bluetooth but *no Wi-Fi at all*. It's a pure Thread endpoint, useless for anything that needs to reach the internet on its own. Espressif's **ESP-Matter** SDK is the path for all of them, built on ESP-IDF. ## What Matter gives you Genuinely valuable things, worth being clear about before the criticisms: - **Real interoperability.** A Matter device works with Apple Home, Google Home, Alexa, and Home Assistant without writing an integration for each. - **Local control.** Commands don't round-trip through a vendor cloud, so they're fast and they work when your internet doesn't. - **A commissioning flow people understand.** Scan a QR code, device joins. No captive portal, no app-specific pairing dance. For a light, a switch, a plug, or a basic sensor you want to say "hey Siri" at, this is exactly right and hard to beat with anything homemade — including [the plain Wi-Fi version of the same build](https://nodrix.live/guides/esp32-smart-home-automation). ## What Matter deliberately doesn't do Here's the part that decides whether Matter is sufficient for your project, and it's not a criticism — these are scope decisions, not gaps waiting to be filled. **Matter defines a baseline, and devices can't exceed it.** The standard specifies device types and their attributes. If your build measures something with no cluster defined for it, Matter has no way to carry it. Industry guidance for people shipping Matter products is explicit: custom features and data types outside the specification require a cloud platform alongside. **No history.** Matter tells a controller what a device's state *is*. It isn't a time-series database, and it doesn't keep a record of what your sensor read last Tuesday. **No telemetry or diagnostics path.** The same guidance notes that OTA updates, telemetry, remote management, and detailed device diagnostics all still need cloud connectivity. Matter carries control, not operational data about your fleet. **Remote access belongs to the ecosystem.** Reaching your devices from outside the house means going through Apple, Google, or Amazon's infrastructure and playing by their rules — not through anything you control. ## Using both, which is the actual answer Once you see the split clearly, the design follows. Matter is a **control interface**. A backend like nodrix is a **data layer**. A device can do both, and for a lot of maker projects it should. Take a greenhouse controller on an ESP32-C6. As a **Matter device**, it exposes a relay so the family can turn the fan on from the Home app and a temperature reading that shows up beside the thermostat. Over [**HTTPS to your own instance**](https://nodrix.live/guides/esp32-https-cloud), it reports soil moisture, light hours, water consumed, and its own battery voltage — variables no Matter cluster describes — with months of history, charts, alerts when a threshold trips, and a read API you can query from a script. Neither replaces the other. Matter makes the device a good citizen of the house; your own backend makes it a good instrument. ## The certification reality For DIY, this is simpler than it sounds and worth knowing before you plan a product. Matter devices carry cryptographic identity — a vendor ID assigned by the CSA, a product ID you assign, and attestation certificates. During development you use **test credentials**, with the declaration marked provisional rather than official, and these work with the major ecosystems for building and testing. For a device on your own network, that's the end of the story. For a device you intend to sell as certified Matter, you go through the CSA, and the process is priced for companies rather than individuals. Nothing prevents you from publishing your design and letting others build it — it just isn't a certified Matter product when they do. ## Notes Matter over Wi-Fi doesn't reduce your device's power draw. It's an application layer over the same radio, so a Matter Wi-Fi sensor has the same battery problem as any Wi-Fi sensor. Thread is where the power advantage lives, and it's the main reason to reach for a C6 over a C3. The ESP32-C6's ability to be a Thread border router is genuinely useful even if you never build a Matter device — an ESP-IDF example turns one into a border router that lets a Home Assistant setup talk to Thread devices without buying a hub. Zigbee runs on the same 802.15.4 radio, so a C6 or H2 can be a Zigbee device instead. If you already have a Zigbee network and no interest in Matter, that path is open on identical hardware. ### FAQ **Q: Do I need a Thread border router to use Matter?** No — that's the most common misconception about Matter. Matter runs over Wi-Fi, Ethernet, and Thread, and a device that speaks Matter over Wi-Fi joins your smart home with no border router at all. Thread is one transport option, chosen for battery devices and mesh coverage, not a requirement of the standard. If you have a classic ESP32 and want a Matter light switch, you can build it today. **Q: Which ESP32 variants can do Thread?** The ones with an 802.15.4 radio: the C6, the H2, and the C5. The classic ESP32, the S3, and the C3 have Wi-Fi and Bluetooth only, so they can be Matter devices over Wi-Fi but never Thread devices. The C6 is the interesting one because it carries both radios on a single chip, which also lets it act as a border router bridging the two networks. **Q: Can I certify a homemade Matter device?** Not practically, and you don't need to for personal use. Certification means going through the CSA, who assign your vendor ID, and it's priced for companies shipping products. For a device on your own network you build with test credentials, which work with the major ecosystems for development. What you can't do is distribute that device to others as a certified Matter product. **Q: Does Matter replace my own IoT backend?** No, and this is the part worth understanding before you invest in either. Matter standardises local control of common device types — it deliberately doesn't do telemetry history, remote access outside your home ecosystem, custom data that has no cluster defined, or detailed device diagnostics. Industry guidance is explicit that those capabilities still require cloud connectivity alongside Matter, not instead of it. **Q: What if my sensor measures something Matter has no cluster for?** Then Matter can't carry it, and that's a design property rather than a gap that will close for you. Matter defines a baseline set of device types and attributes, and a device cannot exceed that baseline within the standard. A soil moisture reading in your own units, or a custom diagnostic your build cares about, needs its own path — which is exactly the case for keeping a data backend alongside. --- ## Guide: Raspberry Pi Zero 2 W to the cloud: when a Linux box beats a microcontroller URL: https://nodrix.live/guides/raspberry-pi-zero-2-w-iot Category: hardware · Board: Raspberry Pi Zero 2 W Send Raspberry Pi Zero 2 W sensor data to a cloud dashboard in plain Python — with an honest account of the two things that decide whether a Pi belongs in your project: it cannot run on batteries, and its SD card will eventually betray you. Every other board on this site is a microcontroller. The Pi Zero 2 W isn't — it's a quad-core Linux computer the size of a stick of gum, and that difference decides everything about where it belongs. You get real Python, pip, ssh, cron, and any library you'd use on a desktop. You also get two liabilities that microcontrollers don't have, and being clear-eyed about both is what separates a Pi project that runs for a year from one that dies quietly in a cupboard. ## When a Pi is the right answer Reach for a Zero 2 W when the work genuinely needs an operating system: - **Real Python libraries.** numpy, pandas, OpenCV, anything on PyPI. No porting, no memory ceiling measured in kilobytes. - **Camera work with actual processing.** Not just capture — decode, transform, analyse, store. - **USB peripherals.** Cameras, SDR dongles, serial adapters, storage. - **Several things at once.** A logger, a web interface, and a periodic upload are three processes, not one carefully interleaved loop. - **Code you'd hate to write in C++.** Sometimes that alone is the deciding factor. Reach for an [ESP32](https://nodrix.live/guides/esp32-https-cloud) or a Pico when the job is reading a sensor and reporting it. A microcontroller will do that [on a battery](https://nodrix.live/guides/esp32-deep-sleep-battery), start in milliseconds, and never corrupt a filesystem. ## The power reality This is the constraint people most often discover too late. A Zero 2 W with Wi-Fi up draws roughly **100–180 mA at 5V** just sitting there, and it has **no hardware deep sleep** — the SoC offers software halt modes rather than the microamp sleep states a microcontroller has. Against an ESP32 in deep sleep, idle draw is something like four orders of magnitude higher. In practice that means an ESP32 sensor node runs a year or more on cells, while a battery-powered Pi needs recharging weekly unless you pair it with a solar panel and a substantial bank. If your project has mains power, none of this matters. If it doesn't, this is the whole decision. ## What you'll need - A **Raspberry Pi Zero 2 W** with Raspberry Pi OS Lite and Wi-Fi configured. - A power supply that can actually deliver — undervoltage causes more Pi weirdness than any other single cause. - Python 3 with `requests` (`pip install requests`). - A **nodrix instance** with a project and a project token. ## The script No library needed. This sends telemetry, drains pending control writes, acknowledges them by id, and loops. It uses the Pi's own CPU temperature so it runs with nothing wired up. ```python #!/usr/bin/env python3 import time import requests HOST = "https://nodrix.you.workers.dev" TOKEN = "tok_your_project_token" HEADERS = {"Authorization": f"Bearer {TOKEN}"} session = requests.Session() # reuse the TLS connection session.headers.update(HEADERS) def cpu_temp_c(): with open("/sys/class/thermal/thermal_zone0/temp") as f: return int(f.read()) / 1000.0 def send_telemetry(metrics): r = session.post(f"{HOST}/v1/telemetry", json={"metrics": metrics}, timeout=10) r.raise_for_status() # 204 on success def apply_control(): r = session.get(f"{HOST}/v1/control", timeout=10) r.raise_for_status() pending = r.json().get("control", []) if not pending: return done = [] for w in pending: if w["variable"] == "led": set_led(w["value"] in (True, "on", 1)) done.append(w["id"]) session.post(f"{HOST}/v1/control/ack", json={"ids": done}, timeout=10) def set_led(on): print(f"led -> {'on' if on else 'off'}") # replace with real GPIO def main(): while True: try: send_telemetry({"cpu_temp": cpu_temp_c()}) apply_control() except requests.RequestException as e: print(f"network error: {e}", flush=True) # keep going; systemd is the safety net time.sleep(30) if __name__ == "__main__": main() ``` The `Session` object is doing real work. Without it, every request renegotiates TLS from scratch — fine occasionally, wasteful every thirty seconds forever. Reusing the connection is one line and it's the difference between a polite client and a noisy one. Catching `RequestException` and continuing matters more on a Pi than you'd think. A Wi-Fi blip that a microcontroller library would silently retry will terminate a naive Python script, and a dead script on a headless box is invisible until someone checks the dashboard. ## Build the dashboard `cpu_temp` appears in your project the first time the script posts — variables are created on sight, so there's nothing to register. Put it on a **chart** widget rather than a value widget: on a Pi the CPU temperature is a genuine health metric, and a Zero 2 W in a sealed case throttles under sustained load, which shows as a ceiling the trace refuses to cross. For the downlink, add a **toggle** bound to a variable named `led`. Flipping it queues a write the script collects on its next pass and acknowledges by id. Confirming that round trip early is worth the two minutes — it proves both directions before you've wired a single GPIO. Then add a **schedule** automation that reports once a day. A headless box in a cupboard is precisely the device you never look at, and a message that arrives proves the whole chain — Pi, network, script, and systemd — was alive to produce it. ## Run it as a service A script you started over ssh dies when your session ends. A device that runs for months needs systemd. Create a unit at `/etc/systemd/system/nodrix-sensor.service` with `After=network-online.target` so it waits for the network, `ExecStart=/usr/bin/python3 /home/pi/sensor.py`, and — the two directives that matter — `Restart=always` and `RestartSec=10`. Then `systemctl enable --now nodrix-sensor`. That gives you three things a background process doesn't: it starts on boot, it comes back if it crashes, and its output goes to the journal, so `journalctl -u nodrix-sensor -f` is your live log from anywhere. Add `flush=True` to your prints — Python buffers stdout when it isn't a terminal, and without it your logs arrive in confusing bursts. ## Making the SD card survive The Pi's other liability is storage. SD cards wear out from writes and corrupt on unclean power loss, and an IoT device is a machine that logs constantly and gets unplugged carelessly. Two mitigations, in increasing order of commitment: **Move logs to RAM.** `log2ram` puts `/var/log` on a ramdisk and writes it back on clean shutdown. Routine logging stops touching the card at all, which addresses the wear half of the problem for very little effort. **Mount the root filesystem read-only with an overlay.** All writes land in a RAM overlay and the card is never written during normal operation, so pulling the power cannot corrupt it. This is the strong option for a device that will be switched off at the wall. Two honest caveats. Read-only root makes changes awkward — you disable the overlay, reboot, edit, re-enable, reboot. And it isn't absolute: if the card's own controller is midway through a wear-levelling operation when power vanishes, it can still be damaged. Lower the odds, don't assume they're zero, and keep a written image of a working card. ## Going further The Pi's real advantage shows once you use the OS. A camera with OpenCV doing local analysis and reporting only results is a genuinely Pi-shaped project — the same send-conclusions-not-frames architecture as the [S3 edge AI build](https://nodrix.live/guides/esp32-s3-edge-ai), with vastly more headroom for the model. It also makes a good gateway. A Pi can collect from [Bluetooth](https://nodrix.live/guides/esp32-ble-sensor-gateway) or serial devices that can't reach the network themselves and forward their readings — a job Linux does naturally, though an ESP32 handles the BLE case on far less power. ## Notes Undervoltage is the most common cause of mysterious Pi behaviour — random reboots, SD corruption, Wi-Fi dropping. Check for it with `vcgencmd get_throttled`; a non-zero result means the supply, not the software. `/sys/class/thermal/thermal_zone0/temp` reports millidegrees, hence the division by 1000. Like the ESP32's die sensor, it measures the chip rather than the room, and it's a useful health metric in its own right — a Zero 2 W under sustained load in a sealed case throttles. Raspberry Pi OS **Lite** is the right image here. The desktop version spends memory and card writes on things a headless sensor will never use. ### FAQ **Q: Can I run a Pi Zero 2 W on batteries like an ESP32?** Realistically, no. A Zero 2 W idles around 100–180 mA with Wi-Fi up, and it has no hardware deep sleep — the SoC only offers software halt modes, so there's no equivalent of an ESP32 sleeping at microamps. The gap is roughly four orders of magnitude. Battery-only Pi sensors end up needing weekly swaps or a solar panel and a sizeable bank, which is usually the moment to reach for a microcontroller instead. **Q: Will the SD card really fail?** Eventually, and unclean power loss accelerates it dramatically. The two standard mitigations are moving logs to a RAM disk so routine writes stop wearing the card, and mounting the root filesystem read-only with an overlay so power loss can't corrupt it. Both help enormously. Neither is absolute — if the card's own controller is mid wear-levelling when power disappears, it can still be damaged. **Q: When is a Pi the better choice over an ESP32?** When the job genuinely needs an operating system. Real Python libraries, a camera with actual image processing, USB peripherals, a local database, several things running at once, or code that would be miserable in C++. The Pi's advantage isn't that it's faster — it's that everything you know from a desktop works, including pip, cron, and ssh. **Q: How do I make my script start on boot and stay running?** A systemd service, not a cron entry or an rc.local line. systemd will start it after the network is up, restart it if it crashes, and give you real logs through journalctl. That single piece of configuration is most of the difference between a script that works when you're watching and a device that stays up for months. **Q: Do I need a special library to talk to nodrix from Python?** No — plain `requests` is enough. The device protocol is a JSON POST with a bearer token and a matching GET for pending commands, so the whole integration is a few dozen lines with no dependency beyond what most Pi projects already have installed. --- ## Guide: Voice control your own ESP32 hardware: three paths, honestly compared URL: https://nodrix.live/guides/voice-control-esp32 Category: concept · Board: ESP32-S3 How to actually add voice control to DIY hardware: fully offline recognition on an ESP32-S3, borrowing Siri or Google through Matter, or natural language through an AI agent — with the constraint that rules out custom wake words for individuals. "Just add voice control" is one of those requests that sounds like a feature and is actually three different projects. Which one you want depends on whether you need it to work offline, whether you mind Apple or Google being in the loop, and whether you want fixed commands or actual conversation. Here are the three paths that genuinely work, what each costs, and the constraint that eliminates the option most people ask for first. ## The constraint worth knowing before you start Almost everyone begins by wanting a custom wake word. "Hey Greenhouse." It is the one part of this you cannot practically build yourself. Wake word detection is an always-on model listening to everything, which makes it unusually demanding: it has to be tiny, run continuously on battery, almost never trigger falsely, and work across every voice and accent. Espressif's own requirements for training one are a good measure of the problem — **recordings from more than 500 people, including at least 100 children, captured in a room quieter than 40 dB.** That's a data-collection programme. For an individual maker it's out of reach, and the honest options are to use one of the pre-trained wake words that ship with the engine, or to commission a model. **Custom *commands*, though, are easy.** Wake words and commands are separate engines. Once the wake word fires, command recognition takes over — and that supports up to **200 commands including your own**. So "Hi ESP, greenhouse fan on" is a weekend project where "Hey Greenhouse, fan on" isn't. The first half is fixed; everything after it is yours. ## Path one: fully offline, on the board Espressif's speech stack runs recognition entirely on-device — no internet, no cloud, no audio leaving the room. **What it needs.** An **ESP32-S3** or **P4** with **PSRAM**. Command recognition doesn't run on the classic ESP32, the C3, or the C6. Same reasoning as [any on-device model](https://nodrix.live/guides/esp32-s3-edge-ai): the S3 has the vector instructions and the memory headroom, the small variants don't. **What it gives you.** A device that works with the internet down, answers instantly with no round trip, and never sends audio anywhere. For a light switch or a fan, that's the correct architecture — local control shouldn't depend on a datacentre. **What it costs.** A fixed wake word, a fixed command grammar, and no ability to handle anything outside the list. It recognises "fan on"; it has no idea what "it's stuffy in here" means. Once a command is recognised, the rest is ordinary firmware — set a pin, and report the change as telemetry so your dashboard reflects what the room just did. ## Path two: borrow Siri, Google, or Alexa The most underrated option, because it involves building no voice infrastructure at all. Make your device a **Matter endpoint**, and every voice assistant in the house works with it immediately. Apple Home, Google Home, and Alexa all speak Matter, so "Hey Siri, turn on the greenhouse fan" works without a microphone on your board, a model, or a wake word. **What it costs** is scope. Matter defines a set of device types, and voice only reaches what fits one — a switch, a light, a thermostat. A custom sensor reading in your own units has no Matter representation and therefore no voice, and remote access runs through the ecosystem's infrastructure rather than yours. The [Matter guide](https://nodrix.live/guides/matter-thread-for-makers) covers that boundary properly. The pragmatic move is to do both: expose the switchable things through Matter so the household gets voice for free, and report everything else to your own backend where it isn't constrained by a standard's device model. ## Path three: talk to an agent The third path replaces voice *commands* with actual language, and it needs no new hardware at all. Nodrix ships an **MCP server**, so an AI client can read your live variable state, set values, create automations, and fire events against your instance. Claude has voice input on phone and desktop. Put those together and you are speaking to something that can act on your hardware — without you building a recognition pipeline. The difference from a voice assistant is not incremental. A voice assistant matches phrases to intents it was configured for. An agent works with your actual data, so it handles requests nobody enumerated in advance: *"is the greenhouse hotter than yesterday?"*, *"which sensor hasn't reported today?"*, *"set up an alert if the tank drops below 20%"*. That last one creates an automation, which no fixed-grammar system could do. **What it costs.** It needs internet, it isn't instant the way a local wake word is, and the MCP server is owner-gated and off by default for good reason — turning it on is covered in [Control your ESP32 with Claude](https://nodrix.live/guides/control-esp32-with-claude-mcp). ## Choosing The paths answer different questions, and plenty of setups use more than one. - **Must work with the internet down, fixed set of actions** → offline on an S3. - **Household should be able to say it, and it's a switch or a light** → Matter, and let the ecosystem do the work. - **You want to ask questions and change behaviour, not just flip things** → an agent over MCP. A greenhouse might reasonably use all three: local voice for the fan so it works during an outage, Matter so anyone can ask Siri, and an agent for the *"why did humidity spike on Tuesday"* questions that are the actual reason you instrumented it. ## Notes Microphone placement matters more than the model. Recognition accuracy in a real room is dominated by distance, reflections, and background noise, and a device on a shelf across the room will perform worse than any tuning can fix. Wake word engines typically support a handful of pre-trained options, and a wake word is normally three to six syllables. Short ones trigger falsely far more often, which is why commercial assistants all use multi-syllable names. Voice is a control path, not a data path. Whichever route you take, the readings should still go to a backend you own — voice tells the room what to do, and it's your dashboard that remembers what happened. ### FAQ **Q: Can I create my own custom wake word?** Realistically, no — and this is the constraint nobody leads with. Espressif's requirements for training a custom wake word include recordings from more than 500 people, at least 100 of them children, captured in a room quieter than 40 dB. That's a data-collection project, not a weekend. You either use one of the pre-trained wake words that ship with the engine, or you pay for a commissioned model. **Q: But I can add my own commands?** Yes, and this is the part that's genuinely easy. Wake words and commands use different engines: the wake word detector is the hard, always-listening one, while command recognition runs only after waking and supports up to 200 commands including your own. So 'Hi ESP, greenhouse fan on' is buildable today — the first half is fixed, the second half is entirely yours. **Q: Which ESP32 can do offline voice?** Command recognition runs on the ESP32-S3 and the P4, and it needs PSRAM. That rules out the classic ESP32, the C3, and the C6 for this job. It's the same reasoning as any on-device model: the S3 has the vector instructions and the memory headroom, and the smaller variants don't. **Q: Do I need to build voice recognition at all?** Often not, and skipping it is underrated. If your device is a Matter endpoint, Siri, Google Assistant, and Alexa already work through whichever ecosystem your household uses — no microphone on your board, no model, no wake word. You're borrowing voice infrastructure that already exists rather than rebuilding it badly. **Q: How does the AI agent path differ from Alexa?** Fixed commands versus actual language. A voice assistant matches phrases to intents, so it handles what it was configured for and nothing else. An agent with access to your instance can act on requests it has never seen — comparing readings, creating an automation, reasoning about why a sensor looks wrong — because it's working with your data rather than matching a phrase. --- ## Guide: Seeed XIAO ESP32 to the cloud: a thumbnail-sized battery sensor URL: https://nodrix.live/guides/xiao-esp32-battery-sensor Category: hardware · Board: XIAO ESP32C3 Build a battery-powered wireless sensor on a Seeed XIAO ESP32C3 that reports to your own cloud dashboard — with the antenna trap that stops most first attempts and the regulator detail that decides whether it runs for weeks or months. A standard ESP32 DevKit is a fine thing on a bench and an awkward thing in an enclosure. It's long, it wants a separate charging module for battery work, and it sits in headers rather than on a board. The Seeed XIAO family solves the physical problem. A XIAO ESP32C3 is **21 × 17.5 mm** — smaller than a postage stamp — with LiPo charging built in and castellated edges so the prototype and the production part can be the same component. This guide builds a battery sensor on one, and covers the two things about these boards that aren't in the marketing. ## First: the antenna trap Start here, because it stops more first attempts than anything else. **The XIAO ESP32C3 has no onboard antenna.** Most ESP32 boards have a ceramic chip antenna or a PCB trace antenna; the C3 XIAO gave up that space to be 21 mm long. Instead it ships with a small external antenna that clips onto a **u.FL connector** beside the USB port. Without that antenna attached, the board powers up, runs your sketch, and never connects to anything. The symptom is indistinguishable from a wrong password, so people spend an hour checking credentials before noticing the tiny connector. Attach it, and the trade turns out to be a good one — an external antenna genuinely outperforms an onboard trace, with usable range well beyond what a DevKit manages. Not every XIAO is the same here. The C6, for instance, has both an onboard antenna and an external connector with a switch between them, so check your specific board rather than assuming. ## Second: the sleep current is the board's, not the chip's This is the detail that decides whether your sensor runs for weeks or months, and it's absent from almost every XIAO tutorial. You'll read that an ESP32-C3 sleeps at a handful of microamps. That's the *chip*. The *board* has a 3.3V regulator, and on the XIAO C3 that part has a quiescent current of roughly **35 µA** all by itself — an order of magnitude more than the sleeping processor, drawn continuously whether your firmware is clever or not. The consequence is a real design decision: - **Powered through the battery pads**, current flows through the regulator, and measured sleep figures land in the hundreds of microamps. - **Powered directly through the 3V3 pin**, the regulator is bypassed, and reported sleep current drops to around **11 µA**. For a sensor that spends 99.9% of its life asleep, that difference is most of your battery life. If you're running from a regulated source anyway, feed 3V3 directly. If you want the onboard charger's convenience, accept the regulator's draw and size the cell for it. Choice of chip matters too. The C6 has been measured around 15 µA sleeping on a basic example, while the S3 is considerably thirstier — worth its power budget when running a camera or a model, wasteful when reading a thermometer. ## What you'll need - A **Seeed XIAO ESP32C3** and its **u.FL antenna** (do not lose it). - A **LiPo cell** for the BAT pads on the underside. - Any I2C sensor — a [BME280](https://nodrix.live/guides/esp32-weather-station) is a good default. - The **Nodrix** Arduino library. - A **nodrix instance** with a project and a project token. ## The firmware A battery sensor shouldn't hold a socket open. The library's HTTP mode exists for exactly this: wake, connect, send, check for pending commands once, and sleep — with no persistent connection to maintain, unlike [the always-on WebSocket build](https://nodrix.live/guides/esp32-https-cloud) a mains-powered board would use. ```cpp #include #include #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; const uint64_t SLEEP_US = 15ULL * 60 * 1000000; // 15 minutes Adafruit_BME280 bme; RTC_DATA_ATTR int bootCount = 0; // survives deep sleep void setup() { bootCount++; Wire.begin(); bme.begin(0x76); // HTTP mode: no persistent socket, built for wake-report-sleep. Nodrix.beginHTTP(WIFI_SSID, WIFI_PASS, HOST, TOKEN); Nodrix.send("temperature", bme.readTemperature()); Nodrix.send("humidity", bme.readHumidity()); Nodrix.send("pressure", bme.readPressure() / 100.0F); Nodrix.send("boot_count", bootCount); Nodrix.flush(); // push before sleeping Nodrix.poll(); // one downlink check esp_deep_sleep(SLEEP_US); } void loop() { } ``` `Nodrix.flush()` before sleeping is the line people forget. Sends are queued, and `esp_deep_sleep` cuts power to the radio without warning — skip the flush and your readings die in a buffer rather than reaching the dashboard. `boot_count` is worth sending on any sleeping device. It costs nothing and it's the fastest way to spot a board that's rebooting more often than it should, which is what a brownout on a tired battery looks like from the outside. ## Build the dashboard The three sensor variables appear on first sight — drop **chart** widgets on temperature and humidity, and a **value** widget on pressure. Put `boot_count` on a **chart** rather than a value widget. As a rising line its slope is your wake rate, and a slope that suddenly steepens means the board is resetting rather than sleeping through its interval. That's a fault you'd otherwise never notice. ## Going further The castellated edges mean this board can be reflowed straight onto a carrier PCB, so the thing you prototyped is the thing you produce. For a sensor you want several of, designing a small carrier with the XIAO as a module beats building the ESP32 circuit yourself. To report battery level, add a divider from the cell to an analog pin and send the voltage as another variable. Check your specific XIAO's wiki first — the family differs on whether a divider is already fitted and which pin it lands on, and assuming the wrong one gives you a confidently wrong reading. For a sensor somewhere cold or distant, the [deep sleep guide](https://nodrix.live/guides/esp32-deep-sleep-battery) covers the power budget properly, including the wake-time costs that dominate once sleep current is handled. ## Notes `RTC_DATA_ATTR` is what lets `bootCount` survive. Deep sleep is a genuine reboot, so ordinary globals reset every cycle and only RTC memory persists. Wake time matters more than sleep current once the regulator question is settled. Every wake spends several seconds with the radio on at tens of milliamps, which dwarfs microamps of sleep — halving your reporting frequency saves far more than any firmware tweak. The XIAO's small size means fewer GPIOs: eleven on the C3, against thirty-plus on a DevKit. For a sensor on an I2C bus that's ample; for a project driving many pins, it's the constraint that sends you back to a larger board. ### FAQ **Q: My XIAO ESP32C3 won't connect to Wi-Fi at all. What's wrong?** Almost certainly the antenna. Unlike most ESP32 boards, the XIAO ESP32C3 has no onboard ceramic antenna — the space went to making the board 21 mm long — so it ships with a small external antenna that plugs into a u.FL connector next to the USB port. Without it attached, the radio is effectively deaf. It's the single most common first-hour problem with this board, and it looks exactly like a credentials bug. **Q: Why is my deep sleep current so much higher than the ESP32 datasheet says?** Because you're measuring the board, not the chip. The XIAO C3's 3.3V regulator alone has a quiescent draw around 35 µA, which dominates the sleep budget and has nothing to do with the ESP32-C3. Powering the board through its 3V3 pin instead of the battery pads bypasses that regulator and drops sleep current dramatically — reported figures fall from a couple of hundred microamps to around ten. **Q: Which XIAO should I use for a battery sensor?** The C3 or the C6 for a plain sensor; not the S3 unless you need its compute. Sleep current differs substantially across the family — the C6 has been measured around 15 µA on a basic sleep example, while the S3 is markedly thirstier. The S3 earns its power budget when you're running a camera or inference, and wastes it when you're reading a temperature. **Q: How do I charge a battery on a XIAO?** Solder a LiPo cell to the BAT pads on the underside and the onboard charger handles it whenever USB is connected. That integration is the main reason to choose a XIAO over a DevKit for portable work — a DevKit needs an external charging module, and the XIAO doesn't. **Q: Can I use a XIAO in a finished product?** That's what the castellated edges are for. The board can be reflowed onto a carrier PCB like a surface-mount module rather than sitting in headers, which makes the same part you prototyped with the part you ship. It's an unusually production-friendly design for something aimed at makers. --- ## Guide: Control your ESP32 with Claude: an MCP server for your own hardware URL: https://nodrix.live/guides/control-esp32-with-claude-mcp Category: project · Board: ESP32 Point Claude at your own IoT backend and let it read your sensors and flip your relays in plain language — no cloud middleman, no vendor skill. nodrix ships a native MCP server, owner-gated and off by default, that turns your ESP32 fleet into tools an AI agent can call. Ask Claude "is the greenhouse too warm?" and have it actually check — not because you pasted a number into the chat, but because it queried the sensor. Then ask it to turn on the fan, and hear the relay click. That's what an MCP server on your IoT backend makes possible, and nodrix ships one natively. This is a different pitch from the AI features bolted onto consumer smart-home apps. There's no vendor cloud in the middle, no pre-baked "skill," and no assistant that only works with devices someone else manufactured. Your ESP32, your firmware, your Cloudflare account — and an AI agent that can read and command all of it through an open protocol, exactly as far as you allow and no further. ## What MCP actually is, in one paragraph The [Model Context Protocol](https://modelcontextprotocol.io) is the open standard AI assistants use to call tools outside their own context. An MCP server publishes a list of tools; the assistant decides when to call them and acts on what comes back. Most MCP servers wrap a SaaS API or a database. When your IoT platform is the server, the tools are your hardware: list the projects, read a variable's live state, pull a time-series, set a control variable, create an automation. Claude stops being a place you describe your system and becomes something that can inspect and operate it. ## The niche this fills It's worth being precise, because the smart-home-plus-AI space is not empty. Home Assistant has shipped an [official MCP server](https://www.home-assistant.io/integrations/mcp_server/) since early 2025, and there are large, active community MCP projects for it. If your setup is off-the-shelf devices on a local hub, that ecosystem is mature and you should use it. What has essentially no coverage is the other half of the maker world: **custom hardware you built, reporting to a cloud you own.** An ESP32 you flashed doesn't live in Home Assistant's device registry, and it doesn't want a local hub to reach it from anywhere. That project — a board on plain HTTPS, a dashboard on your Cloudflare account, and now an AI agent that can drive it — is the gap. nodrix fills it as a first-party feature rather than a bridge you assemble. ## What the server exposes Two tiers of tools, gated separately on purpose. **Read tools** (available whenever the server is on): - `list_projects`, `list_variables` — discover what exists. - `get_state` — the current value of a variable, as last reported by the device. - `get_series` — the time-series history, for "what did CO2 do overnight." - `list_dashboards`, `list_widgets`, `list_automations`, `list_integrations` — the shape of your setup. **Management tools** (behind a second switch): - `set_variable` — write a control variable. This is the one that flips a relay: set the variable your device's `NODRIX_WRITE` handler watches, and the board acts. - `create_automation`, `update_automation`, `run_automation` — build and fire the trigger → condition → action logic. - `create_dashboard`, `update_widget`, `create_variable`, `create_integration`, and the matching `update_*` tools — construct the rest of the setup in language. There are deliberately **no delete tools**. An agent can build, read, and command; it cannot destroy. ## Turning it on safely The safety model is the reason this is a feature rather than a footgun. Everything is off until you decide otherwise: 1. **The server is off by default.** Until the owner enables it, the MCP endpoint returns 404 — it doesn't exist as far as the internet is concerned. 2. **Only the owner can enable it.** It lives in **Settings → More**, behind the owner role. No member or admin can expose your hardware to an assistant. 3. **Writes are a separate switch.** Turn the server on and it comes up **read-only** — an assistant can see your data but cannot touch a single variable. The management tools stay dark until you flip the write flag too, so "let Claude look at my sensors" and "let Claude control my house" are two distinct, deliberate decisions. The practical result: an LLM can never command your hardware by default. You grant reading, then — if and when you want it — writing, as two separate acts. ## Connecting Claude The server speaks standard MCP over two endpoints on your instance: an **OAuth** endpoint at `/v1/mcp/oauth` for interactive clients like the Claude apps, and a **bearer-token** endpoint at `/v1/mcp` for token-configured clients like Claude Code and IDE plugins. For the Claude apps, connect it as a custom connector — the whole path is five steps: 1. **Enable the server in nodrix first.** In your instance, go to **Settings → More** and turn the MCP server on; until you do, the endpoint returns 404 and nothing can connect. Turn on the write flag here too if you want Claude to control hardware and not just read it. 2. **Open Claude's connector settings.** In the browser, click your profile icon → **Settings**; on desktop, press `⌘⇧,` (macOS) or `Ctrl+,`. Then click **Connectors** in the sidebar. 3. **Add a custom connector.** Click **Add** (top-right) → **Add custom connector**, paste your instance's OAuth URL — `https://your-instance.workers.dev/v1/mcp/oauth` — and click **Add**. 4. **Approve on your own instance.** Claude redirects you to your nodrix instance to sign in as the owner and approve the connection. That consent screen is where you see exactly what you're granting before anything is shared. 5. **Use the tools.** Back in Claude, your instance appears under the **+** ("Add files, connectors, and more") menu in the message box. From the connector's settings you can enable or disable individual tools — a second place the read-only boundary is yours to draw. For **Claude Code or an IDE plugin**, point it at the bearer endpoint `/v1/mcp` instead, with an `Authorization: Bearer ` header carrying a token from your instance. If a menu has moved, Anthropic's [custom-connectors guide](https://support.anthropic.com/en/articles/11175166-getting-started-with-custom-connectors-using-remote-mcp) has the canonical version of these steps. From there it's conversation: - "What's the current soil moisture in the greenhouse project?" → `get_state`. - "Plot CO2 for the last 24 hours and tell me if it ever crossed 1000 ppm." → `get_series`, then analysis. - "Turn on the exhaust fan." → `set_variable`, and the ESP32's handler fires. (Only if you enabled writes.) - "Make an automation that alerts me on Telegram when the freezer goes above -10." → `create_automation`, wired to the integration you already set up. The last two are the ones that feel like the future: you described an outcome, and the agent assembled the platform primitives to make it real — no dashboard clicks, no YAML. ## Why this beats the bolt-on approach You could get some of this by giving Claude a generic HTTP tool and your API docs. The native server is better in the ways that matter: - **It's typed and discoverable.** The assistant sees named tools with schemas, not a REST surface it has to reverse-engineer, so it calls them correctly the first time. - **It's scoped.** Read-only really means read-only; the boundary is enforced server-side, not requested politely in a prompt. - **It's yours.** The whole path is your instance on your Cloudflare account. No third-party AI-IoT service is brokering access to your devices, and nothing about it can be discontinued on you. ## Where it's going The interesting frontier isn't voice-controlling one bulb — Home Assistant does that well. It's an agent with read access to a fleet of your own sensors and the judgment to reason across them: correlate the energy monitor's spike with the temperature log, notice the greenhouse trend before it becomes a problem, draft the automation and let you approve it. The tools to do that ship in the box today; the assistant supplies the reasoning. Turn the server on read-only, connect Claude, and ask it what it notices about your data — it's the fastest way to see why this is more than a novelty. MCP is the half that lets an assistant *act* on your instance. The other half is knowledge: a [Claude Skill for ESP32](https://nodrix.live/guides/claude-skill-for-esp32) teaches it to write correct firmware in the first place, so "add a sensor and check it's reporting" becomes one request instead of three sessions. And if what you actually wanted was to talk to the hardware, [voice control](https://nodrix.live/guides/voice-control-esp32) compares this path against on-device recognition and borrowing Siri or Alexa outright. ## Notes - **Off by default, owner-gated, writes separate.** Three deliberate gates before an AI touches hardware; no delete tools ever. - **Your own endpoint.** MCP served from your Cloudflare account — no AI-IoT vendor in the path. - **Open protocol.** Standard MCP; Claude is the reference client, but any MCP-capable assistant connects the same way. ### FAQ **Q: What is an MCP server, and why would my IoT platform have one?** MCP (Model Context Protocol) is the open standard AI assistants like Claude use to call external tools. An MCP server exposes a set of tools an agent can invoke — and when your IoT backend is the server, those tools are your devices: read this sensor, set that variable, create an automation. It's the difference between pasting sensor readings into a chat and letting the assistant query and control the hardware directly. **Q: Is it safe to let an AI control my hardware?** nodrix is built so the answer stays yes. The MCP server is off by default and only the instance owner can enable it. Even then it comes up read-only: the management tools that can write a variable or create an automation are behind a second switch, so an assistant can look at your data without any ability to command hardware until you explicitly turn writes on. There are no delete tools at all. You're granting capabilities deliberately, one gate at a time. **Q: How is this different from the Home Assistant MCP server?** Home Assistant's MCP server is built for Home Assistant's world — off-the-shelf smart-home devices on your local network, exposed through its Assist API. nodrix's is built for the other world: your own custom hardware — an ESP32 you flashed — reporting to your own cloud over plain HTTPS. If your project is a store-bought bulb, use Home Assistant. If it's a board you wrote the firmware for, this is the path that doesn't require running a local hub. **Q: Which AI assistants can connect?** Anything that speaks MCP. Claude (Desktop, Code, and the web connector) is the reference client, and the server also works with other MCP-capable tools. nodrix exposes both a bearer-token endpoint for programmatic clients and an OAuth endpoint for the ones that authenticate interactively, so you connect whichever way your client expects. **Q: Does Claude need my hardware to be online to answer questions?** For live state, yes — reading a sensor's current value asks your instance, which holds what the device last reported. But the history lives in your instance regardless, so Claude can analyze a week of temperature data whether or not the board is awake right now. A deep-sleeping battery sensor's last reading is always queryable. --- ## Guide: ESP32 air quality monitor with a live CO2 dashboard URL: https://nodrix.live/guides/esp32-air-quality-monitor Category: project · Board: ESP32 A complete SCD41 build: NDIR-grade CO2, temperature and humidity from one sensor, streamed to a live dashboard with colour-banded alerts. A CO2 monitor is the rare sensor project that changes your behaviour: watch the number climb through a closed meeting room and you will open a window before anyone gets a headache. This build measures CO2 properly with a Sensirion SCD41, reads temperature and humidity from the same part, and streams all three to a live dashboard with colour-banded thresholds — so a glance tells you whether the air is fine or stale. What it doesn't need is the thing most SCD41 tutorials quietly assume: a Home Assistant server running the ESPHome add-on on your network. The ESP32 here posts straight to your own cloud dashboard over plain HTTPS. Nothing local to keep alive, and the readings open from anywhere. ## Why the SCD41 Air-quality projects live or die on the sensor, and the cheap default is a trap. An MQ-135 costs two dollars and reports something, but not CO2 in real ppm — it's a metal-oxide sensor that drifts, needs constant recalibration, and can't separate CO2 from other gases. The older MH-Z19 is a real NDIR CO2 sensor and a fine choice a few years ago. The Sensirion SCD41 is the current answer: a photoacoustic CO2 sensor accurate to about ±(40 ppm + 5% of reading), that also hands you temperature and humidity over the same I2C bus — three variables, one part, no analog guesswork. It's low-power enough for battery builds, and its automatic self-calibration keeps it honest over time. It costs more than an MQ-135, and it's worth every cent the first time the number tells you something true. ## What you'll need - An **ESP32** dev board (any common DevKit variant). - A **Sensirion SCD41** breakout (SCD40 works too — slightly lower accuracy, same code). - Four jumper wires; the SCD41 is I2C, so it's just power and two data lines. - The **Arduino IDE** with the ESP32 board package, the **Nodrix** library, and Sensirion's [**I2C SCD4x** library](https://github.com/Sensirion/arduino-i2c-scd4x), both from the Library Manager. - A **nodrix instance** with a project and a project token. ## Wiring Pure I2C — four wires, no analog pins, no level shifting (the SCD41 breakout is 3.3V-friendly): | From | To | Wire | |------|----|------| | SCD41 VDD | ESP32 3V3 | Power | | SCD41 GND | ESP32 GND | Ground | | SCD41 SDA | ESP32 GPIO21 | I2C data | | SCD41 SCL | ESP32 GPIO22 | I2C clock | GPIO21/22 are the ESP32's default I2C pins. The SCD41 draws a brief high current during each measurement, so power it from a stable 3.3V rail rather than a long, thin lead. ## The firmware The SCD41 measures on its own cadence — one reading every five seconds in periodic mode — so the sketch starts it, then reports whatever it has each cycle. The [nodrix Arduino library](https://github.com/decoded-cipher/nodrix-sdk) owns the socket and reconnects. ```cpp #include #include #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; SensirionI2cScd4x scd4x; void setup() { Wire.begin(); scd4x.begin(Wire, 0x62); scd4x.stopPeriodicMeasurement(); // clean state after a reset scd4x.startPeriodicMeasurement(); Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } void loop() { Nodrix.run(); static unsigned long lastReading = 0; if (millis() - lastReading >= 30000) { lastReading = millis(); uint16_t co2 = 0; float temperature = 0, humidity = 0; if (scd4x.readMeasurement(co2, temperature, humidity) == 0 && co2 > 0) { Nodrix.send("co2", (int)co2); Nodrix.send("temperature", temperature); Nodrix.send("humidity", humidity); } } } ``` Worth understanding rather than copying: - **The `co2 > 0` guard matters.** The SCD41 returns 0 ppm when a measurement isn't ready yet; sending it would draw a false floor on the chart. Skipping keeps the history honest. - **Give it warm-up time.** The first readings after power-up settle as the sensor references itself — expect a couple of minutes before the number is trustworthy, and leave automatic self-calibration on so it stays that way. - **Thirty seconds is plenty.** CO2 in a room moves over minutes, not seconds. Reporting twice a minute catches every meaningful change and keeps the traffic trivial. - **Pin TLS before you ship.** `Nodrix.begin()` connects encrypted but unverified on first run; add `Nodrix.setCACert()` for production, covered in [Connect an ESP32 over HTTPS](https://nodrix.live/guides/esp32-https-cloud). ## Build the dashboard Four widgets, each bound to a variable the firmware sends: | Widget | Bind to | Shows | |---|---|---| | Gauge | `co2` | live CO2 in ppm, with colour bands | | Chart | `co2` | the day's air-quality rhythm | | Value | `temperature` | room temperature | | Value | `humidity` | relative humidity | Set the CO2 gauge's bands to the levels that mean something: green below 800 ppm, amber 800–1200, red above 1200. Now the dashboard reads at a glance — you don't interpret a number, you see a colour. The chart is the quietly useful part: a room's CO2 has a shape, climbing while it's occupied and closed, dropping when it's ventilated or empty. Once you know your baseline, the anomalies — the meeting that ran long, the bedroom that never airs out — jump out. ## Add the alert One automation: trigger on a new `co2` reading, condition **above 1200**, action: Telegram — "CO2 at {{value}} ppm — open a window." Add hysteresis so a room hovering at the line doesn't ping you repeatedly: alarm above 1200, and only rearm once it drops back below 900. Both are edited in the dashboard, not the firmware, and the channel swaps to Discord, Slack, or SMS without touching the condition — the full pattern is in [ESP32 notifications](https://nodrix.live/guides/esp32-notifications). ## Going further - **Make it portable.** On a battery with deep sleep, it's a monitor you carry room to room — swap the socket for wake-report-sleep over HTTP per [ESP32 battery life](https://nodrix.live/guides/esp32-deep-sleep-battery), and budget for the SCD41's warm-up on each wake or use its single-shot low-power mode. - **Add particulates.** A PMS5003 alongside the SCD41 adds PM2.5/PM10 — more variables, more widgets, same reporting loop, for a fuller indoor-air picture. - **Watch several rooms.** One SCD41-on-ESP32 per room, each reporting `co2_bedroom`, `co2_office`, and so on; a widget per room and one shared automation. - **Automate the fix.** If a room has a fan or HRV you can switch, add a relay and a `NODRIX_WRITE` handler and let high CO2 turn ventilation on — closed-loop, the pattern from the [smart-home build](https://nodrix.live/guides/esp32-smart-home-automation). ## Notes - **No broker, no local server.** The board speaks HTTPS; the dashboard is on your Cloudflare account. No Home Assistant, no MQTT, nothing on your LAN to keep alive. - **A sensor you can trust.** Real photoacoustic CO2 plus temperature and humidity from one part — no MOX drift, no analog calibration ritual. - **Scales by repeating.** One sketch runs a room or a building; add nodes, not complexity. ### FAQ **Q: Why the SCD41 instead of an MQ-135 or MH-Z19?** Because it measures CO2 honestly. The MQ-135 is a cheap MOX gas sensor that doesn't report CO2 in real ppm — it drifts, needs constant recalibration, and conflates gases. The MH-Z19 is a genuine NDIR CO2 sensor and a reasonable older choice, but the Sensirion SCD41 is a current photoacoustic CO2 sensor that also gives you temperature and humidity from one I2C part, with ±(40 ppm + 5%) accuracy. For a monitor you'll trust enough to act on, the SCD41 is the right sensor in 2026. **Q: Do I need Home Assistant or a local server for this?** No — and that's the point. Most ranking SCD41 tutorials assume you already run Home Assistant with the ESPHome add-on, which means a local server humming 24/7 just to see a number. Here the ESP32 posts readings straight to your own cloud dashboard over HTTPS. Nothing local to run, and the dashboard opens from anywhere. **Q: What CO2 levels should I actually worry about?** Outdoor air is around 420 ppm. Below 800 ppm indoors is comfortable and well-ventilated; 800-1200 ppm is where drowsiness and reduced concentration start; above 1200 ppm the room needs air, and sustained levels over 1500-2000 ppm are worth fixing. The guide bands the dashboard on those thresholds so a glance tells you whether to open a window. **Q: Why does the SCD41 need a couple of minutes to read correctly?** It self-calibrates and the photoacoustic measurement settles after power-up — early readings can be off until it warms in. Give it a few minutes on first boot, and leave its automatic self-calibration enabled so it re-references to fresh air over days. If you run it somewhere that never sees outdoor-level CO2, disable ASC and calibrate manually instead. **Q: Can one board watch several rooms?** Not one board, but one dashboard. Put an SCD41 on an ESP32 in each room, have each report co2, temperature, and humidity under its own variable names, and add a widget per room. The automations and alerting are shared — the pattern scales by repeating the node, not by rewiring anything. --- ## Guide: ESP32 project ideas that connect to the cloud: 10 real builds, ranked URL: https://nodrix.live/guides/esp32-project-ideas Category: project · Board: ESP32 Ten ESP32 IoT project ideas worth actually building — ranked by usefulness, each with the sensor, the difficulty, and a real build guide, not a one-line summary. From a first temperature monitor to a Claude-controlled greenhouse, every one reports to a dashboard you own. Most "ESP32 project ideas" lists are written to be skimmed, not built — fifty one-line summaries from a parts vendor, each ending where the actual work begins. This one is the opposite: ten projects worth genuinely building, ranked by how useful the result is, each with the sensor it needs, its real difficulty, and a full build guide behind it rather than a sentence. Two things they share. Every one reports to a dashboard you **own** — deployed to your own Cloudflare account, no per-device fee and no freemium cap to hit halfway through a semester. And every one is a variation on the same skeleton: read a sensor, send it over HTTPS, see it live, act on it. Build the first and the rest are remixes. ## How these are ranked By usefulness of the finished thing — would you keep it running after it works — with difficulty noted so you can start where you're comfortable. If you're new, start at the top and work down; the skeleton never changes, only the sensor and the logic. ## 1. Temperature & humidity monitor — the one to start with **Sensor:** BME280 · **Difficulty:** beginner · **Build:** the loop every other project reuses. Four wires, a dozen lines, and the whole IoT loop in miniature: sensor to Wi-Fi to live dashboard to phone alert. It's the "hello world" of connected hardware, and worth building even if you don't need it, because it's the foundation the other nine stand on. Add barometric pressure and it graduates into a [weather station](https://nodrix.live/guides/esp32-weather-station). ## 2. Weather station — your microclimate, charted **Sensor:** BME280 · **Difficulty:** beginner · **Build:** [ESP32 weather station](https://nodrix.live/guides/esp32-weather-station). Temperature, humidity, and the barometric pressure that warns of an incoming storm before the clouds arrive — measuring your actual balcony, not a regional forecast. Runs for months outdoors on a battery, and the falling-pressure alert is the kind of thing you'll actually trust. ## 3. Air quality / CO2 monitor — the one that changes your behaviour **Sensor:** Sensirion SCD41 · **Difficulty:** beginner · **Build:** [ESP32 air quality monitor](https://nodrix.live/guides/esp32-air-quality-monitor). Watch CO2 climb through a closed room and you will open a window before anyone gets a headache. A real photoacoustic sensor (not a drifting MQ-135), colour-banded thresholds, and an alert when the air goes stale. The rare monitor whose readings you act on daily. ## 4. Plant watering system — closed-loop and hands-off **Sensor:** capacitive soil moisture + pump · **Difficulty:** beginner · **Build:** [ESP32 plant watering](https://nodrix.live/guides/esp32-automatic-plant-watering). The first project that does something back: reads soil moisture, and when it dries out, runs a pump — with the watering logic in the cloud so you retune it without reflashing. Your introduction to two-way control and safe relay switching. ## 5. Energy meter — the standout capstone **Sensor:** PZEM-004T · **Difficulty:** intermediate · **Build:** [ESP32 energy meter](https://nodrix.live/guides/esp32-energy-meter). Real volts, amps, watts, and a lifetime kWh counter on a live dashboard, with a load-spike alert. It's the project that pays for itself — you'll find the always-on device quietly dominating your bill — and it demonstrates everything an examiner wants to see: real measurement, history, alerting, data you own. The strongest single choice for a final-year project. ## 6. GPS tracker — a live map you control **Sensor:** NEO-6M GPS · **Difficulty:** beginner · **Build:** [ESP32 GPS tracker](https://nodrix.live/guides/esp32-gps-tracker). A marker that follows your vehicle or asset across a map dashboard, with a speed alert — and no proprietary tracking cloud in the loop, unlike every incumbent tutorial. Honest about where Wi-Fi tracking works and where it needs cellular, which is more than most guides manage. ## 7. Smart home controller — switch the house from anywhere **Sensor:** relays + your appliances · **Difficulty:** intermediate · **Build:** [ESP32 smart home](https://nodrix.live/guides/esp32-smart-home-automation). Lights and appliances switched from one private dashboard, with scenes, schedules, and a sunset trigger running the house — no commercial hub, no vendor cloud. The project that turns "I read a sensor" into "I control my home." ## 8. Multi-channel notifier — alerts done right **Sensor:** any + the alert logic · **Difficulty:** beginner · **Build:** [ESP32 notifications](https://nodrix.live/guides/esp32-notifications). Less a single build than a technique every project above reuses: send alerts to Telegram, Discord, Slack, or SMS with zero secrets in your firmware, the threshold and channel editable without reflashing. Build it as a freezer monitor; apply it everywhere. ## 9. Battery sensor node — months on one cell **Sensor:** any + deep sleep · **Difficulty:** intermediate · **Build:** [ESP32 battery life](https://nodrix.live/guides/esp32-deep-sleep-battery). The skill that makes half this list deployable where there's no USB power: deep sleep, RTC-memory Wi-Fi caching, and a real power budget that takes a sensor to months on a single charge. Learn it once, apply it to the weather station, the air monitor, the tracker. ## 10. Claude-controlled hardware — the frontier **Sensor:** any + the MCP server · **Difficulty:** intermediate · **Build:** [Control your ESP32 with Claude](https://nodrix.live/guides/control-esp32-with-claude-mcp). Point an AI assistant at your own instance and let it read your sensors and — if you allow it — flip your relays in plain language. "Is the greenhouse too warm? Turn on the fan." No ESP32 project list anywhere else has this yet, because it needs a platform with a native MCP server. It's the most future-facing thing you can build on an ESP32 right now, and it's genuinely a few clicks away once a sensor is reporting. ## Picking yours - **First ever project?** Start at #1, then #2 or #3 — same skeleton, more interesting output. - **Final-year / capstone?** #5 (energy) or #7 (smart home) for depth, or #10 (Claude) to stand out. Examiners reward closed loops and data ownership; all three have them. - **Something genuinely useful around the house?** #3 (air quality) and #5 (energy) are the two you'll still be running a year later. ## Beyond the ten The list above is ranked for a first build. These are the ones worth doing next — narrower, more specific, and each solves a problem you actually have. - **[Water tank level monitor](https://nodrix.live/guides/esp32-water-tank-monitor)** — ultrasonic depth, percentage full, and an alert before the tank runs dry. - **[Fridge and freezer alarm](https://nodrix.live/guides/esp32-freezer-alarm)** — probes in both compartments, and an alarm that survives the power cut that killed the freezer. - **[Solar and battery monitor](https://nodrix.live/guides/esp32-solar-battery-monitor)** — real amp-hour counting on an INA226, for a cabin, shed, RV, or boat. - **[Vibration monitor](https://nodrix.live/guides/esp32-vibration-monitor)** — motor health in the RMS velocity that ISO 10816 actually grades machines on, rather than raw g. - **[E-paper dashboard](https://nodrix.live/guides/esp32-epaper-dashboard)** — the one build that reads your data back instead of feeding it, and runs for months on a cell. - **[Bluetooth sensor gateway](https://nodrix.live/guides/esp32-ble-sensor-gateway)** — bridge five-dollar BTHome thermometers into your own cloud, one board for the whole house. - **[LoRa gateway](https://nodrix.live/guides/esp32-lora-gateway)** — put a sensor kilometres from the nearest Wi-Fi. And two that aren't projects so much as things every deployed board eventually needs: [Wi-Fi provisioning](https://nodrix.live/guides/esp32-wifi-provisioning), so you stop hardcoding credentials, and [OTA updates](https://nodrix.live/guides/esp32-ota-updates), so you stop carrying boards back to your desk. ## Where they all start Every one of these starts the same way — [get an ESP32 reporting over HTTPS](https://nodrix.live/guides/esp32-https-cloud) to [an instance on your own Cloudflare account](https://nodrix.live/guides/deploy-nodrix-cloudflare) — and branches from there. Build the skeleton once, and this whole list becomes an afternoon each. ### FAQ **Q: What's a good first ESP32 IoT project for a beginner?** A temperature-and-humidity monitor on a BME280, reporting to a cloud dashboard. It's four wires, a dozen lines of firmware, and it teaches the whole loop — sensor to Wi-Fi to dashboard to alert — without any risky wiring or moving parts. Once that works, every other project on this list is a variation on the same skeleton, which is exactly why it's the one to start with. **Q: What makes a good final-year or capstone IoT project?** One that closes the loop and owns its data. Anyone can read a sensor and print it; the projects that stand out add real logic (thresholds, automations), two-way control (the dashboard flips a relay), and data ownership (it runs on infrastructure you control, not a freemium cloud that caps you). The energy monitor, the smart-home controller, and the Claude-controlled build on this list all demonstrate those, which is what turns a demo into a project worth presenting. **Q: Do these ESP32 projects need paid cloud services?** No. Every build here reports to a nodrix instance you deploy to your own Cloudflare account, which for student-scale telemetry sits inside Cloudflare's free plan — no per-device fees, no message caps, no data-retention limit counting down. That matters for a project you'll run for months or demo repeatedly: the free tiers of hosted IoT platforms are sized to run out exactly when your project starts working. **Q: Can I do these ESP32 projects on an ESP8266 or Pico W instead?** Most of them, yes. The simpler sensor projects run fine on an ESP8266 (mind its tighter RAM) or a Pico W in MicroPython. The heavier builds — always-on WebSocket control, several sensors at once — are more comfortable on an ESP32. Each guide notes where the board choice matters; the cloud side is identical whichever you pick. **Q: How long does a typical ESP32 IoT project take to build?** A first sensor-to-dashboard project is an afternoon. The mid-list builds — energy meter, air-quality monitor, GPS tracker — are a weekend once you're comfortable with the loop. The complexity is almost never the firmware, which the device library keeps short; it's the physical build (wiring a relay safely, calibrating a sensor, mounting the enclosure), which is the part worth taking your time on. --- ## Guide: Build an ESP32 weather station with a live cloud dashboard URL: https://nodrix.live/guides/esp32-weather-station Category: project · Board: ESP32 A complete ESP32 weather station on the BME280: temperature, humidity, and barometric pressure streamed to a live dashboard you open from anywhere — with a falling-pressure storm alert — no MQTT broker, no legacy IoT cloud, on your own Cloudflare account. A weather station is the project that makes a maker check a dashboard every morning. This one measures the weather where you actually are — temperature, humidity, and the barometric pressure that warns of an incoming storm — with a single BME280, and streams it to a live dashboard you open from your phone anywhere. No regional-forecast API guessing at your microclimate; the pressure on your own balcony, charted. Most ESP32 weather-station tutorials stop one step short: they read the sensor and print it to the Serial Monitor or a tiny OLED, then wave at "connect it to the cloud" as a next step. This one is the cloud step, done properly — over plain HTTPS to your own Cloudflare account, no MQTT broker and no legacy freemium platform holding your history. ## Why the BME280 The default two-in-one sensor, the DHT22, measures temperature and humidity and nothing else. The BME280 measures those plus **barometric pressure**, from the same small I2C part — and pressure is what earns the name "weather station." A steadily falling barometer is the oldest reliable storm signal there is, hours ahead of the clouds. The BME280 is also more accurate than a DHT22 and speaks clean I2C instead of the DHT's slow, occasionally-flaky one-wire timing. For a few cents more, it's the sensor every serious build uses. (One caution when buying: the BMP280 is the cheaper sibling with no humidity. For a weather station you want the BME — the E has humidity.) ## What you'll need - An **ESP32** dev board (any common DevKit variant). - A **BME280** breakout (I2C version — most are). - Four jumper wires. - The **Arduino IDE** with the ESP32 board package, the **Nodrix** library, and Adafruit's [**BME280** library](https://github.com/adafruit/Adafruit_BME280_Library) (it pulls in the Adafruit Unified Sensor library), from the Library Manager. - A **nodrix instance** with a project and a project token. ## Wiring I2C, four wires, no analog: | From | To | Wire | |------|----|------| | BME280 VCC | ESP32 3V3 | Power | | BME280 GND | ESP32 GND | Ground | | BME280 SDA | ESP32 GPIO21 | I2C data | | BME280 SCL | ESP32 GPIO22 | I2C clock | Mount the BME280 away from the ESP32 on a short lead. The board's own heat will bias the temperature reading if the sensor sits right against it — a couple of centimetres and some airflow fixes it. ## The firmware Read three values, send three variables, on a gentle cadence — weather moves slowly. The [nodrix Arduino library](https://github.com/decoded-cipher/nodrix-sdk) handles the connection. ```cpp #include #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; Adafruit_BME280 bme; void setup() { bme.begin(0x76); // some breakouts are at 0x77 Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } void loop() { Nodrix.run(); static unsigned long lastReading = 0; if (millis() - lastReading >= 60000) { lastReading = millis(); Nodrix.send("temperature", bme.readTemperature()); Nodrix.send("humidity", bme.readHumidity()); Nodrix.send("pressure", bme.readPressure() / 100.0F); // Pa → hPa } } ``` Worth understanding rather than copying: - **Pressure in hPa.** The BME280 reports pascals; dividing by 100 gives hectopascals (millibars), the unit weather reports use — sea-level pressure sits around 1013 hPa, so your readings should land near there once you account for altitude. - **A minute between readings is generous.** Weather doesn't change in seconds. On mains power a minute is fine; on a battery you'd stretch it to 5–15 minutes and deep-sleep between. - **Check the I2C address.** BME280 breakouts are at 0x76 or 0x77 depending on the board — if `begin()` fails, try the other. - **Pin TLS before you ship.** `Nodrix.begin()` connects encrypted but unverified on first run; add `Nodrix.setCACert()` for production, covered in [Connect an ESP32 over HTTPS](https://nodrix.live/guides/esp32-https-cloud). ## Build the dashboard | Widget | Bind to | Shows | |---|---|---| | Value | `temperature` | current temperature | | Value | `humidity` | relative humidity | | Gauge | `pressure` | barometric pressure, ~980–1040 hPa | | Chart | `pressure` | the pressure trend — the storm-teller | | Chart | `temperature` | the day's temperature curve | The pressure chart is the one to watch. Absolute pressure matters less than its slope: a slow rise means settling, fair weather; a steady fall over a few hours is the classic sign of an approaching low and likely rain. Once you've watched it for a week against what the sky actually did, you'll read an incoming front off that line before any app tells you. ## Add the storm alert Weather stations reward one good automation. A rapid pressure drop is the signal worth a notification: trigger on a new `pressure` reading, and alert when it falls meaningfully below where it sat a few hours ago (a threshold around 1000 hPa is a reasonable absolute floor for "weather coming" in many regions; tune it to yours). Action: Telegram — "Pressure dropping, {{value}} hPa — weather likely turning." Swap the channel for Discord or SMS without touching the logic, per [ESP32 notifications](https://nodrix.live/guides/esp32-notifications). ## Going further - **Take it outside on a battery.** Deep sleep makes an outdoor node last months; house it in a vented radiation shield to keep sun and rain off the sensor — see [ESP32 battery life](https://nodrix.live/guides/esp32-deep-sleep-battery). - **Add rain and wind.** A tipping-bucket gauge (a counted reed switch), an anemometer, and a wind vane each add a variable and a widget without changing the loop's shape. - **Compare to the forecast.** Pull your own history through the read API and chart it against a weather service's numbers — your microclimate almost never matches the regional forecast exactly, and the gap is the interesting part. - **Run several stations.** Sun and shade, indoors and out, ground and roof — each node reports its own variables to one dashboard. ## Notes - **Your microclimate, not a regional guess.** The sensor measures your location; the API can't. - **No broker, no legacy cloud.** HTTPS to your own Cloudflare account — no MQTT, no ThingSpeak or Blynk caps, history that stays yours behind the read API. - **Battery-friendly by design.** Slow weather plus deep sleep equals months on a cell. ### FAQ **Q: Why a BME280 and not a DHT22?** The DHT22 gives temperature and humidity and stops there. The BME280 adds barometric pressure over the same tiny I2C part — and pressure is what makes it a weather station rather than a thermometer, because a falling barometer is the classic leading indicator of an incoming storm. The BME280 is also more accurate and doesn't have the DHT22's slow, occasionally-flaky one-wire protocol. For a few cents more it's the obvious pick. **Q: Do I need an internet weather API for this?** No — this measures your actual location, which is the point. An API tells you the regional forecast; your BME280 tells you the pressure on your balcony, the humidity in your greenhouse, the temperature in the shade of your garden. The two are complementary, but the sensor is the one that knows your microclimate, and it keeps working when the API rate-limits you. **Q: How do I measure rain and wind too?** Add them as more variables. A tipping-bucket rain gauge is a reed switch you count with an interrupt; anemometer and wind vane kits output pulses and a voltage you read the same way. Each becomes another Nodrix.send call and another widget — the reporting loop and dashboard don't change shape, they just gain series. This guide covers the temperature/humidity/pressure core that every station shares. **Q: Why does my BME280 read a few degrees high?** Self-heating. The ESP32 and the sensor's own draw warm the board, and a BME280 mounted right against it reads the board's heat, not the air's. Mount the sensor away from the ESP32 on a short lead, give it airflow, keep it out of direct sun, and if you deep-sleep the board between readings the self-heating largely disappears — another reason a battery weather station reads more accurately than a always-on one. **Q: Can this run outdoors on a battery?** Yes, and it's the natural form. Deep sleep between readings takes an ESP32 weather node to months on a single cell, and weather changes slowly enough that a reading every 5-15 minutes is plenty. Put the electronics in a vented enclosure (a Stevenson-screen-style shield keeps sun and rain off the sensor), and report on each wake over HTTP — the pattern is in the battery guide linked below. --- ## Guide: Raspberry Pi Pico 2 W vs ESP32: which to pick in 2026 URL: https://nodrix.live/guides/raspberry-pi-pico-2-w-vs-esp32 Category: comparison · Board: Raspberry Pi Pico 2 W ESP32 for wireless maturity and the deeper ecosystem, Pico 2 W for newer silicon and cleaner MicroPython. Both around $7 — here's how to choose. **Pick the ESP32** if the project reports to a cloud dashboard. Its wireless stack is more mature, and the ecosystem of reconnect handling, TLS, and cloud examples is far deeper. **Pick the Pico 2 W** if you want newer silicon and the cleanest MicroPython on any microcontroller. They're within a few dollars of each other, so ecosystem fit decides this, not price. The Raspberry Pi Pico 2 W put real pressure on the ESP32's default-board status: newer silicon, the Raspberry Pi name and documentation, and a genuinely lovely MicroPython experience, all around seven dollars. So "Pico 2 W or ESP32" is now a fair fight — and most of the comparisons answering it either predate the Pico 2 W entirely or lean on secondhand benchmark claims with no code to back them. Here's the honest version, aimed at the question makers actually have: which board for a project that connects to a cloud dashboard. ## What the Pico 2 W brings The Pico 2 W is built on the RP2350, and it's a genuinely modern part: - **Dual-architecture silicon.** The RP2350 carries both Arm Cortex-M33 and RISC-V cores — you pick which to run — which is a first at this price and a sign of how current the design is. - **Excellent MicroPython.** The Pico's flagship path is MicroPython, and it's among the most pleasant on any microcontroller: clean, well-documented, fast to iterate. - **The Raspberry Pi pedigree.** Documentation, longevity, and a foundation behind the board — the same reasons people trust the bigger Pis. - **Programmable I/O (PIO).** A standout hardware feature: state machines that generate or capture precise digital signals, which makes bit-banging odd protocols genuinely easy. ## What the ESP32 brings The ESP32's advantages are the kind that only accrue with time in the field: - **Mature wireless.** Wi-Fi is the ESP32's original purpose, and a decade of IoT projects have hardened the networking stack, the reconnect handling, and the TLS code. - **The deepest ecosystem.** More libraries, more examples, more Stack Overflow answers, more cloud-connection tutorials than any comparable board — when you hit a problem, someone has already solved it publicly. - **A family of variants.** Need Bluetooth, more compute, or Thread/Zigbee? There's an ESP32 variant for it — the C3, S3, C6 — where the Pico line is essentially one wireless board. - **Two strong frameworks.** Happy in the Arduino framework or ESP-IDF, with a device library that can hide the Wi-Fi, TLS, and reconnect work entirely. ## Head to head | | Raspberry Pi Pico 2 W | ESP32 (classic / variants) | |---|---|---| | Silicon | RP2350, Arm + RISC-V | Xtensa or RISC-V, by variant | | Flagship language | MicroPython | Arduino C++ or ESP-IDF | | Wireless maturity | Good, newer | Deep, battle-tested | | Ecosystem size | Growing | The largest in the class | | Standout feature | PIO (programmable I/O) | Variant choice, BLE, mature TLS | | Bluetooth | Yes | Yes (classic + BLE) | | Price (2026) | ~$7 | ~$4–10 | Two honest notes the freshest incumbents still get wrong: the boards are at price parity, so cost isn't the tiebreaker; and performance claims you'll see quoted (interrupt speed, current draw) are often secondhand and untested — measure them yourself for your workload rather than trusting a number copied between articles. ## For a cloud dashboard project specifically This is where the abstract comparison gets concrete. Both boards speak plain HTTPS, so both reach a cloud dashboard without a broker. The difference is how much the ecosystem does for you: - **On the ESP32**, a device library can own the Wi-Fi, the TLS handshake, the reconnect logic, and the control channel — your sketch is just `Nodrix.send` and a handler. The maturity shows up as less code you write and fewer edge cases you hit. Built end to end in [Connect an ESP32 over HTTPS](https://nodrix.live/guides/esp32-https-cloud). - **On the Pico 2 W**, MicroPython with `urequests` posts readings cleanly, and the code reads beautifully — you're just a bit closer to the metal on reconnects and TLS. Built end to end in [Pico W to the cloud with MicroPython](https://nodrix.live/guides/raspberry-pi-pico-w-iot-dashboard). Crucially, the dashboard doesn't care. Point either board at your own instance and the readings land in the same widgets, the same automations fire, the same alerts go out. You can even run both — a Pico 2 W in one room and an ESP32 in another — reporting to one dashboard, which is a good way to decide with your own hands which you'd rather build on. ## The bottom line Pick the **Pico 2 W** if you love MicroPython and want current silicon with the Raspberry Pi foundation behind it. Pick the **ESP32** if you want the deepest ecosystem and the most mature wireless — which, for a first cloud-connected project, is usually the safer default simply because every problem you'll hit already has a published answer. They're close enough in 2026 that you won't regret either, and since [your own instance](https://nodrix.live/guides/deploy-nodrix-cloudflare) runs both identically, you can change your mind later without changing your cloud. ### FAQ **Q: Is the Raspberry Pi Pico 2 W better than an ESP32?** Neither is strictly better — they optimise for different things. The Pico 2 W has newer silicon (a dual-architecture RP2350: Arm plus RISC-V cores), excellent MicroPython support, and the Raspberry Pi documentation pedigree. The ESP32 has more mature Wi-Fi, a vastly larger library and example ecosystem, and years of proven cloud-IoT deployments. For a first wireless project the ESP32's ecosystem depth usually wins; for a project that values clean MicroPython and current silicon, the Pico 2 W is compelling. **Q: Which is cheaper, Pico 2 W or ESP32?** They're at rough price parity in 2026 — a Pico 2 W is around $7, ESP32 dev boards run roughly $4–10 depending on variant. Price isn't the deciding factor between them the way it is between an ESP8266 and an ESP32. Choose on ecosystem and use-case fit, not on saving a dollar. (Worth noting: Raspberry Pi's larger boards saw price rises in 2026 on memory costs, but the microcontroller Picos stayed low.) **Q: Which has better Wi-Fi for IoT?** The ESP32's wireless is more mature — Wi-Fi and networking are its original reason for existing, and a decade of IoT projects have hardened the stack and the libraries. The Pico 2 W's wireless works well and MicroPython makes it pleasant, but the ecosystem of cloud-connection examples, reconnect handling, and battle-tested TLS code is deeper on the ESP32 side. For a connect-to-the-cloud project specifically, that maturity is a real advantage. **Q: Can I use the Pico 2 W with the Arduino IDE like an ESP32?** You can, via the Arduino-Pico core, but the Pico's most natural and best-supported path is MicroPython — that's where its documentation and community are strongest. The ESP32 is happiest in either the Arduino framework or ESP-IDF. If you strongly prefer Arduino C++, the ESP32's support for it is more established; if you like MicroPython, the Pico 2 W is a joy. **Q: Which should I pick for a cloud dashboard project?** Both speak plain HTTPS, so both reach a cloud dashboard — the choice is ecosystem fit. The ESP32 has more ready-made cloud examples and a device library that hides the Wi-Fi, TLS, and reconnect work; the Pico 2 W does it cleanly in MicroPython with urequests. Point either at your own instance and the dashboard doesn't care which board sent the reading — nodrix has walkthroughs for both. --- ## Guide: The best free IoT platforms for makers in 2026, ranked URL: https://nodrix.live/guides/best-free-iot-platforms Category: comparison Every free IoT tier caps something — devices, messages, or retention. An honest 2026 ranking of what each one limits, and where the free tier is your own. Every "free" IoT platform is free until your project works. Then the caps arrive: a device limit you hit by adding a second sensor node, a message quota that continuous monitoring empties in a week, a retention window that quietly deletes the data you meant to keep. None of that is a scam — hosted platforms have real costs — but it means "which free tier" is a real engineering decision, not a signup form. This is a ranked list of the free options makers actually weigh in 2026, judged on what the free tier honestly sustains. One disclosure up front: nodrix is our platform. It's also ranked first for a structural reason you can verify yourself — it's the one option on this list whose free tier belongs to Cloudflare, not to an IoT vendor with an upgrade funnel. ## The short version | Platform | Model | The free tier's real limit | First paid step | |---|---|---|---| | nodrix | Open source, your Cloudflare account | Cloudflare free-plan quotas (generous) | Cloudflare usage pricing | | Blynk | Hosted SaaS | Few devices, monthly message quota, short history | Steep — Pro is ~$99/month | | ThingSpeak | Hosted (MathWorks) | Update-rate floor, annual message cap | Annual license tiers | | Adafruit IO | Hosted SaaS | Data-rate cap, 30-day retention | IO+ subscription | | Arduino Cloud | Hosted SaaS | Tight thing/device limits, ecosystem pull | Monthly plans | | Datacake | Hosted SaaS | Per-device model from the start | Per-device pricing | | TagoIO | Hosted SaaS | Handful of devices, data-ops metering | Usage-based paid plans | | ThingsBoard | Open source + hosted cloud | Cloud has no free tier; self-host is heavy | ~$10/month cloud, or your server | ## 1. nodrix — free the way infrastructure is free nodrix is open source (MIT). There is no hosted nodrix service and no vendor free tier: you one-click **deploy it to your own Cloudflare account**, and the only quotas that exist are Cloudflare's — which, on the free plan, comfortably absorb hobby and small-fleet telemetry at rates that would blow through any hosted cap on this list. Devices speak plain HTTPS/WebSocket through an open Arduino library (ESP32/ESP8266) or raw HTTP from anything else; dashboards, automations, and a read API are in the box; every reading stays in your tenancy. The honest trade: you're operating your own instance — one click to deploy, but yours. There's no vendor support desk, and a native mobile app is still on the roadmap (dashboards are responsive web). If a managed service is what you want, one of the hosted options below fits better. **The free tier sustains:** continuous multi-device monitoring, indefinitely — the workload the hosted tiers below are specifically sized to exclude. ## 2. Blynk — the polished app, until the cliff Blynk's mobile app is still the best in class, and for point-a-phone-at-a-microcontroller projects the experience is hard to beat. The free tier is a genuine trial: a few devices, a monthly message quota, days-not-months of history. The structural problem is the cliff after it — the Pro plan runs about $99/month, with little between hobby and professional. Great for evaluating; expensive the moment a real project outgrows the cap. The full comparison is in [our Blynk alternative guide](https://nodrix.live/guides/blynk-alternative). ## 3. ThingSpeak — the academic workhorse Backed by MathWorks, stable for over a decade, and still the default in university coursework thanks to MATLAB analytics. The free tier's shape is distinctive: an annual message allowance and a minimum interval between updates, which suits slow environmental logging and rules out anything chatty. If your project reports every few seconds, the update-rate floor is the wall you'll hit first — the details are in [our ThingSpeak alternative guide](https://nodrix.live/guides/thingspeak-alternative). ## 4. Adafruit IO — friendly, capped by design The most beginner-friendly onboarding in the hosted group, excellent docs, and honest pricing. The free tier caps the data rate and retains data for 30 days — fine for a first project, limiting for anything that needs a year of history. IO+ is reasonably priced as hosted plans go. Where it sits against self-hosting is in [our Adafruit IO alternative guide](https://nodrix.live/guides/adafruit-io-alternative). ## 5. Arduino Cloud — smooth inside the fence If you're all-in on Arduino hardware and the Arduino IDE, the integration is genuinely smooth — sketch sync, device provisioning, dashboards in one place. The free plan's thing limits are tight, and the deeper cost is architectural: the workflow pulls you toward the Arduino ecosystem end to end. For any-board projects, [the comparison](https://nodrix.live/guides/arduino-cloud-alternative) covers where it pinches. ## 6. Datacake — per-device from day one A clean low-code dashboard builder with strong LoRaWAN support. The free tier is a couple of devices, and the paid model is per-device — predictable for a fixed fleet, punishing for the add-a-sensor-every-month style of maker growth. Compared in [our Datacake alternative guide](https://nodrix.live/guides/datacake-alternative). ## 7. TagoIO — capable, metered A capable platform with real analytics, and a free tier of a handful of devices metered by data operations. It shows up in expert maker roundups for good reason, but the metering model means you budget "operations" the way you'd budget an API bill — workable, just never free-feeling. ## 8. ThingsBoard — free software, not a free service ThingsBoard Community Edition is genuinely capable open source — the catch is that, as of mid-2026, the hosted cloud publishes no free tier (maker plans start around $10/month), so "free ThingsBoard" means self-hosting a Java application with PostgreSQL and a message broker on a server you run and patch. If you have the box and the appetite, it's the most featureful self-host in the list; the operational weight is the trade, and it's exactly the weight [a serverless deploy avoids](https://nodrix.live/guides/thingsboard-alternative). ## How to actually choose - **A weekend demo** → any hosted free tier works; pick the onboarding you like. Adafruit IO and Blynk are the smoothest. - **A project that runs for years** → count messages per month before you build. Continuous monitoring at even one reading per 10 seconds is ~260k messages a month per variable — check that number against any hosted cap on this list, then check what the paid step costs. - **A classroom or club** → per-account device caps multiply badly. One self-deployed instance for the whole cohort sidesteps the accounting. - **Data you intend to keep** → retention windows and export paths matter more than dashboards. Prefer platforms where history sits behind an API you control. The pattern behind the ranking: hosted free tiers are sized to end. That's their job. The only free tier that doesn't expire with your project's success is one attached to general-purpose infrastructure — which is the argument for [deploying your own](https://nodrix.live/guides/deploy-nodrix-cloudflare) and letting Cloudflare's free plan be the cap. ### FAQ **Q: Is there a completely free IoT platform?** Truly free means no device caps, no message quotas, and no retention limits — and no hosted platform offers that, because your data costs them money. The closest thing is running open-source software on infrastructure with a generous free tier: nodrix on Cloudflare's free plan is free in that sense, and self-hosted ThingsBoard is free if you already own a server and the time to run it. **Q: What's the catch with hosted free tiers?** They're sized for a demo, not a deployment. A couple of devices, a message quota that continuous monitoring burns through in days, and short data retention. That's fair — they're funnels to paid plans — but it means the free tier decision is really a pricing decision: check what the first paid step costs before you build on the free one. **Q: Which free tier is best for a classroom or student projects?** For a room full of students, per-account device caps bite immediately. ThingSpeak remains a common classroom pick because MATLAB-adjacent coursework tolerates its update-rate floor. A single nodrix deploy on one Cloudflare account can host every student's project with no per-device accounting, which is why it works well for cohorts. **Q: Do any of these lock me in?** Watch two things: whether the device protocol is open (can you point firmware elsewhere without a rewrite?) and whether your historical data can leave (is there a bulk export or API?). Vendor SDKs and proprietary protocols are the usual lock; platforms speaking plain HTTP/MQTT/WebSocket with a real read API are the easy ones to walk away from — which, paradoxically, is a good reason to trust them. --- ## Guide: ESP8266 vs ESP32-C3: upgrade new builds, keep working ones URL: https://nodrix.live/guides/esp32-c3-vs-esp8266 Category: hardware · Board: ESP32-C3 The ESP32-C3 is the 8266's successor at nearly the same price, with the RAM headroom TLS needs. Upgrade new designs; leave working deployments alone. **For a new design, take the ESP32-C3.** It costs about the same as an ESP8266 and removes the RAM ceiling that makes TLS a squeeze. **For a deployment that already works, leave it alone** — the 8266 is not obsolete, and reflashing a fleet to fix nothing is not an upgrade. The 8266's remaining advantages are real, and they are covered below. The ESP8266 is the board that made Wi-Fi microcontrollers a hobby: a decade of tutorials, a sub-two-dollar price, and millions of deployed nodes still dutifully reporting. It's also a 2014 design whose RAM budget makes modern TLS feel like packing a suitcase by sitting on it. The ESP32-C3 is Espressif's designed successor — near-8266 pricing with the constraints removed — and "should I switch" has become the default question at the budget end of the family. ## What the C3 fixes The upgrades that actually change a maker's day, in order: - **RAM headroom.** The C3 has several times the usable memory of the 8266, whose ~40–50 KB of practical heap made every TLS handshake a negotiation. On the C3, HTTPS with full certificate validation is a non-event — the difference between pinning a fingerprint and hoping and pinning a CA properly. - **Bluetooth LE 5.** Provisioning without hardcoded credentials, beacons, phone-adjacent tricks — a whole category the 8266 simply doesn't have. - **Hardware crypto.** TLS handshakes lean on acceleration instead of grinding the core. - **Saner pins.** More usable GPIO without the 8266's minefield of boot-strapping pins that reset the board if a sensor holds them low at the wrong moment — and deep-sleep wake without the famous GPIO16-to-RST bodge wire. - **A current toolchain.** RISC-V core, first-class attention in today's SDK and Arduino core, and the same platform generation as the C6 above it. ## What the ESP8266 still has - **Price and ubiquity.** Under $2 in bulk, in every parts drawer, on every tutorial site — the cheapest ticket to Wi-Fi there has ever been. - **A decade of documented behavior.** Every quirk has a forum thread; the platform holds no surprises, which is its own kind of reliability. - **Continued life.** Espressif's 15-year production commitment carries the series toward the end of the decade, and the software ecosystem still sees genuine investment — this is a supported legacy, not abandonware. - **It already works.** A deployed 8266 that reports on schedule owes you nothing. Boards in service are not an upgrade queue. ## ESP32 vs ESP8266 is no longer two chips The reason this page is about the C3 specifically, rather than "ESP32 vs ESP8266" as a binary, is that the ESP32 stopped being one chip years ago. It's a family now, and which variant you pick usually matters more than the family name: - **ESP32-C3** — this page: near-8266 price, modern RAM, BLE, the natural successor for a cheap Wi-Fi node that has outgrown the 8266. - **Classic ESP32** — dual-core and ubiquitous, the default when you want the deepest ecosystem and the most examples to copy from. - **ESP32-S3** — more compute and vector instructions for camera work or light on-device ML. - **ESP32-C6** — Wi-Fi 6 plus the 802.15.4 radio for Thread and Zigbee, the family's route into Matter; the detail is in [ESP32-C6 for makers](https://nodrix.live/guides/esp32-c6-for-makers). So the old question — "should I use an ESP32 or an ESP8266?" — almost always resolves to "move to a modern ESP32 variant," and then to "which one." At the budget end where the 8266 lived, that's the C3; step up only when a project needs an S3's compute or a C6's radios. The two-chip duel has quietly become a shortlist, and the 8266's price is the one thing that still keeps it on the list at all. ## The firmware reality With the [nodrix Arduino library](https://github.com/decoded-cipher/nodrix-sdk), both chips run the same sketch — same `Nodrix.begin`, same `Nodrix.send`, same `NODRIX_WRITE` handlers. The one line that differs is how you pin TLS, and it's the whole story of the two chips in miniature: ```cpp #include void setup() { #if defined(ESP8266) Nodrix.setFingerprint(HOST_FP); // chain validation is heavy for 40 KB of heap #else Nodrix.setCACert(ROOT_CA_PEM); // C3: validate the chain properly, RAM is a non-issue #endif Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } void loop() { Nodrix.run(); } ``` The 8266 route works — [the full walkthrough](https://nodrix.live/guides/esp8266-iot-dashboard) squeezes BearSSL into that budget deliberately, and a fingerprint pin is honest security. But a fingerprint pins one certificate rather than a chain, so certificate rotation upstream means re-pinning; a CA pin on the C3 shrugs rotation off. That maintenance difference compounds over a fleet's lifetime. Everything else in a migration is pin numbers and the pleasant deletion of workarounds: the ADC that's no longer a single overloaded pin, the wake wire that's no longer soldered, the payload size you stop budgeting. ## Choosing, compressed | Situation | Pick | |---|---| | New design, any TLS ambition, BLE, or growth plans | ESP32-C3 | | Absolute-minimum-cost fixed sensor, modest reporting | ESP8266, still | | Deployed 8266 fleet, working fine | Leave it be | | Buying for the drawer in 2026 | C3 — or [a C6](https://nodrix.live/guides/esp32-c6-for-makers) for the radio options | | Deep-sleep battery sensor | Either — [the pattern](https://nodrix.live/guides/esp32-deep-sleep-battery) fits both; the C3 skips the wake-wire bodge | ## The bottom line The ESP8266 earned its retirement-age respect, and nothing about 2026 forces it out of service. But the design conversation is over: the C3 costs pennies more and deletes the exact constraints — RAM, TLS comfort, pin quirks, no BLE — that every 8266 project spends its first evening working around. Point either one at [your own instance](https://nodrix.live/guides/deploy-nodrix-cloudflare) and the cloud can't tell them apart — the difference is the workarounds you no longer write. ### FAQ **Q: Is the ESP8266 obsolete in 2026?** No — dated, not dead. Espressif's longevity commitment keeps the ESP8266 series in production toward the end of the decade, the ecosystem still receives real investment, and boards cost under two dollars in bulk. For a Wi-Fi-only sensor with modest TLS needs it remains perfectly serviceable. Obsolete is the wrong frame; 'no longer what you'd design in' is the right one. **Q: What does the ESP32-C3 actually improve over the ESP8266?** The ones that change your day: several times the usable RAM, which turns TLS from a heap-anxiety exercise into a non-event; Bluetooth LE 5 for provisioning or beacons; hardware crypto acceleration; more usable GPIO with saner boot-strapping quirks; and a current toolchain that gets first-class attention. The CPU jump (a 160 MHz RISC-V core vs the 8266's aging Tensilica) matters less than the memory for typical projects. **Q: Do I have to change my code to move from ESP8266 to ESP32-C3?** Less than you'd fear. Arduino-side, most sketches move with pin-number edits — and with the nodrix library the cloud calls are literally identical. The one visible difference is TLS pinning: on the 8266 you pin a certificate fingerprint (setFingerprint) because full chain validation is heavy for its RAM; on the C3 you pin the CA properly (setCACert) and stop thinking about the heap during handshakes. **Q: Should I buy ESP8266 boards for a new project just because they're cheaper?** Only if the project is genuinely at the two-dollar end: a fixed sensor, one HTTPS report every few minutes, no BLE, no growth ambitions — the 8266 still does that with dignity. The moment the plan includes always-on TLS connections, bigger payloads, OTA headroom, or Bluetooth anything, the one-dollar saving buys you the exact constraints the C3 was designed to remove. --- ## Guide: ESP32-C6 for makers: what the new radios change, and what they don't URL: https://nodrix.live/guides/esp32-c6-for-makers Category: hardware · Board: ESP32-C6 The ESP32-C6 packs Wi-Fi 6, BLE, and an 802.15.4 radio for Thread and Zigbee into a $5 board — the ESP32 line's ticket into Matter. Here's what that actually means for a maker project today, the radio fine print nobody leads with, and why your cloud firmware doesn't change at all. The ESP32-C6 is the chip that made the smart-home crowd take notice: Wi-Fi 6, Bluetooth LE, and an IEEE 802.15.4 radio — the physical layer under [Thread and Zigbee](https://nodrix.live/guides/matter-thread-for-makers) — on one die, on dev boards that cost about five dollars. It's become one of the most popular chips in the ESPHome and Home Assistant world on the strength of that radio list, and it's Espressif's ticket into the Matter era. This page is the maker's-eye view: what those radios actually buy you today, the fine print the spec sheet doesn't lead with, and the part that matters if your project talks to a cloud dashboard — which is that nothing changes at all. ## What the C6 actually is A single RISC-V core at 160 MHz with the modern peripheral set, plus the most interesting radio package Espressif has shipped at this price: - **Wi-Fi 6 (802.11ax) on 2.4 GHz** — current-generation Wi-Fi, including Target Wake Time. - **Bluetooth LE 5** — the usual provisioning and beacon duties. - **802.15.4** — the mesh radio protocol under **Thread** and **Zigbee**, which is what makes the C6 **Matter-capable** both over Wi-Fi and over Thread. Worth saying plainly: it's a single-core chip. The classic dual-core ESP32 still wins raw compute, and the C6 isn't a straight upgrade — it's a current-generation radio platform. For read-a-sensor, run-a-relay, talk-to-the-cloud projects, one core is plenty. ## The radio fine print The three protocols share **one 2.4 GHz radio**. Coexistence is real and SDK-supported, but it's time-slicing, and the practical shape in today's firmware stacks is choosing a primary personality per build: this board is a Wi-Fi device, or it's a Zigbee sensor, or it's a Thread node. Toolchains reinforce it — Zigbee and Thread live in specific SDK configurations, and the mainstream Arduino path is Wi-Fi-first. A C6 gives you the option of any of them on the same hardware; it doesn't give you all of them at full strength at once. Wi-Fi 6 comes with its own asterisk: its dense-network efficiency and Target Wake Time need an 802.11ax access point on the other side, and TWT support across routers and firmware is still uneven. A battery project shouldn't be planned around TWT yet — a deep-sleeping board already schedules its own radio, on any access point ever made. ## What this means for a cloud project: nothing, pleasantly Here's the part that keeps the C6 boring in the best way. A dashboard you can open from anywhere, telemetry history, alerts to your phone — that's cloud-backend work, and it rides ordinary Wi-Fi and HTTPS, which the C6 speaks at least as well as every ESP32 before it. The [nodrix Arduino library](https://github.com/decoded-cipher/nodrix-sdk) targets the esp32 core, and the C6 is just another entry in the boards menu: ```cpp #include NODRIX_WRITE("relay") { digitalWrite(10, value.asBool()); } void setup() { pinMode(10, OUTPUT); Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } void loop() { Nodrix.run(); } ``` Same `Nodrix.send`, same `NODRIX_WRITE`, same one socket. One devkit footnote: the official C6-DevKitC's onboard "LED" is an addressable RGB on GPIO8 that wants `neopixelWrite()`, not `digitalWrite()` — which is why the example above drives a plain GPIO instead. Every guide on this site — the [HTTPS walkthrough](https://nodrix.live/guides/esp32-https-cloud), the [deep-sleep battery patterns](https://nodrix.live/guides/esp32-deep-sleep-battery), the project builds — runs on a C6 without a changed line beyond pin numbers. ## When do the extra radios earn their keep? - **You want the same hardware to serve two futures.** Today it's a Wi-Fi sensor on your dashboard; next year maybe it reflashes as a Thread device in a Matter fabric. One $5 board covers both bets. - **You're building for a smart-home ecosystem on purpose** — a device that Apple Home or Google Home should discover natively. That's Matter's job, the C6 is the budget entry point, and it's a local-interoperability goal, distinct from (and combinable with) your own cloud dashboard — [the Home Assistant comparison](https://nodrix.live/guides/home-assistant-vs-nodrix) draws that boundary. - **Zigbee sensors without a commercial hub** — the C6 as a DIY Zigbee endpoint is an active, fast-moving corner of the ESPHome world, tooling caveats included. If none of those describe your project, buy the C6 anyway when it's convenient — it's cheap, current, and fine — but know you're buying optionality, not capability your dashboard will notice. ## The bottom line The ESP32-C6 is the right default board to reach for in 2026: current silicon, every relevant radio, five dollars. Just read its promise correctly — it's one radio wearing three hats, the smart-home protocols are a per-build choice, and the cloud side of your project is gloriously indifferent to all of it. Point it at [your own instance](https://nodrix.live/guides/deploy-nodrix-cloudflare) and the newest chip in the family behaves exactly like the family: a few lines of firmware, a live dashboard, nothing else to run. ### FAQ **Q: Should I buy an ESP32-C6 instead of a regular ESP32 for a new project?** If you're buying new anyway and the board fits your form factor, the C6 is a sensible default: current-generation silicon at effectively the same price, with radio options you may grow into. But it's a single RISC-V core where classic ESP32s are dual-core — for most sensor-and-dashboard projects that difference is irrelevant, and nothing about a Wi-Fi cloud project requires a C6. Don't replace working boards for it. **Q: Can the ESP32-C6 run Wi-Fi and Zigbee at the same time?** They share one 2.4 GHz radio, so it's time-slicing, not two radios. Coexistence support exists in the SDK, but the practical reality in common firmware stacks is that you build for a primary role — a Wi-Fi device, or a Zigbee/Thread device — rather than a router doing both heavily at once. Treat 'all the protocols' as a menu, not a buffet plate. **Q: Does Wi-Fi 6 make my IoT project faster or longer-lived on battery?** Only with a Wi-Fi 6 router on the other end, and mostly in dense environments. The headline battery feature, Target Wake Time, lets a device negotiate scheduled wake-ups with the access point — genuinely promising for battery sensors, but it needs router support and firmware maturity, and a deep-sleeping board already controls its own schedule. Buy the C6 for the option, not the promise. **Q: Does the nodrix Arduino library work on the ESP32-C6?** Yes — the library targets the esp32 Arduino core, and current core releases cover the C6. The same sketch that runs on a classic ESP32 runs unmodified: Nodrix.begin, Nodrix.send, NODRIX_WRITE. Wi-Fi plus HTTPS/WebSocket is deliberately the most portable path across the whole ESP32 family. **Q: Do I need Matter or Thread for a cloud dashboard project?** No — they solve a different problem. Matter and Thread are about making devices interoperable with smart-home ecosystems (Apple Home, Google Home, Alexa) on the local network. A dashboard you open from anywhere, history, and alerts are a cloud-backend problem, which runs over ordinary Wi-Fi and HTTPS. Plenty of projects will eventually do both; they don't compete. --- ## Guide: Build an ESP32 energy meter with a live dashboard URL: https://nodrix.live/guides/esp32-energy-meter Category: project · Board: ESP32 A complete ESP32 energy monitor: read real volts, amps, watts, and kWh with a PZEM-004T, push them to a live dashboard over one WebSocket, and get a Telegram alert when the load spikes — no broker, no vendor app, on your own Cloudflare account. Most ESP32 energy-meter tutorials you'll find are built on platforms that have since changed out from under them — deprecated app flows, retired tokens, code that no longer compiles as written. This build has no such dependency. A PZEM-004T does the metering, the ESP32 reports over plain HTTPS/WebSocket, and the dashboard, history, and alerts run in nodrix on **your own Cloudflare account** — nothing in the path can be discontinued on you. What you get: live volts, amps, watts, and a lifetime kWh counter on a dashboard you can open from anywhere, a 24-hour load curve, and a Telegram message when something draws more than it should. ## Safety first This project meters mains electricity. The ESP32 side is all low-voltage, but the PZEM's input terminals connect to live line and neutral: - **De-energize the circuit** before touching any mains wiring, every time. - **Enclose everything.** No exposed screw terminal when powered — a cheap junction box is fine. - **Fuse the voltage tap** with a small inline fuse (0.5A is plenty; it only feeds the meter). - The **CT clamp is non-invasive** — it clips around one insulated conductor (line or neutral, never both) and touches no copper. If any of that reads as unfamiliar rather than routine, build the firmware against a bench supply and have an electrician land the mains side. ## What you'll need - An **ESP32** dev board (any common DevKit variant). - A **PZEM-004T v3.0** with its split-core CT coil — the version with the Modbus serial interface. - A **5V supply** for the ESP32, an enclosure, and a fused tap off the circuit you're metering. - The **Arduino IDE** with the ESP32 board package, the **Nodrix** library, and the [**PZEM004Tv30**](https://github.com/mandulaj/PZEM-004T-v30) library, both from the Library Manager. - A **nodrix instance** with a project and a project token. ## Why use dedicated metering hardware The classic DIY route — an SCT-013 current clamp into the ESP32's ADC with EmonLib — measures only current and assumes a mains voltage to estimate watts. Real mains sags and swells a few percent all day, the ESP32's ADC is famously nonlinear, and reactive loads (fridges, motors, anything with a power supply) make apparent and real power diverge. The PZEM-004T v3.0 samples voltage and current together in purpose-built metering silicon and hands the ESP32 six finished numbers over serial: volts, amps, watts, kWh, hertz, and power factor. It measures 80–260V, up to 100A with the external CT, and keeps its energy count through power cuts. The ESP32's job collapses to what it's good at: asking for numbers and shipping them to the cloud. ## Wiring Two sides, kept physically apart. The mains side: line and neutral into the PZEM's voltage terminals (through the fuse), and the CT clipped around the line conductor. The low-voltage side is four wires: | From | To | Wire | |------|----|------| | PZEM 5V | ESP32 3V3 | Power | | PZEM GND | ESP32 GND | Ground | | PZEM TX | ESP32 GPIO16 (RX2) | Serial | | PZEM RX | ESP32 GPIO17 (TX2) | Serial | Power the PZEM's interface side from the ESP32's **3.3V pin**, not 5V. The comms side is optically isolated from the mains-side metering chip and runs happily at 3.3V — and it keeps the PZEM's TX at levels the ESP32's RX pin (which is not 5V-tolerant) is built for. If your particular module only talks when powered at 5V, keep the 5V supply but put a two-resistor divider on the PZEM-TX line. ## The firmware One socket carries everything: readings go up, and the connection stays open for anything you add later (a relay, a counter reset). The [nodrix Arduino library](https://github.com/decoded-cipher/nodrix-sdk) owns the socket and the reconnects; the sketch is just a read-and-send loop. ```cpp #include #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; PZEM004Tv30 pzem(Serial2, 16, 17); void setup() { Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } void loop() { Nodrix.run(); static unsigned long lastReading = 0; if (millis() - lastReading >= 10000) { lastReading = millis(); float voltage = pzem.voltage(); float current = pzem.current(); float power = pzem.power(); float energy = pzem.energy(); if (isnan(voltage)) return; // meter not answering — skip, don't send garbage Nodrix.send("voltage", voltage); Nodrix.send("current", current); Nodrix.send("power", power); Nodrix.send("energy_kwh", energy); } } ``` Worth understanding rather than copying: - **The `isnan` check matters.** When the PZEM sees no mains voltage (breaker off, loose tap) it returns NaN for everything. Skipping the send keeps the chart honest — a gap reads as "meter offline", a stream of zeros reads as "house off", and those are different diagnoses. - **The kWh counter lives in the meter.** `pzem.energy()` is a lifetime total that survives reboots and outages on both sides. Report it as-is; derive per-day or per-billing-period numbers in the cloud from the stored series. - **Ten seconds is a deliberate rate.** Fast enough to catch a kettle, slow enough to be free on your own infrastructure — and a rate that monthly-capped hosted tiers can't sustain. - **Pin TLS before you ship.** `Nodrix.begin()` connects encrypted but unverified on first run; add `Nodrix.setCACert()` for production, covered in [Connect an ESP32 over HTTPS](https://nodrix.live/guides/esp32-https-cloud). ## Build the dashboard Four widgets, each bound to a variable the firmware is already sending: | Widget | Bind to | Shows | |---|---|---| | Gauge | `power` | live draw in watts | | Chart | `power` | the 24-hour load curve | | Value | `energy_kwh` | lifetime energy total | | Value | `voltage` | mains health at a glance | The chart is where the project pays for itself. A day of household load has a shape — the overnight baseline, the morning spike, the compressor sawtooth of a fridge. Once you know your baseline, two things become obvious: what's always on (that's the number that dominates the bill), and anything new that shouldn't be. A baseline that steps up 60W and stays there is how you find the amplifier that never sleeps. ## Add the alerts Two automations cover the useful cases; both are edited in the dashboard, not the firmware. **Load spike.** Trigger on a new `power` reading, condition **above 3000** (tune to your circuit), action: Telegram — "Drawing {{value}}W right now." A washing machine tripping this at 2 p.m. is routine; a space heater tripping it at 3 a.m. is worth a message. **Meter offline.** A schedule trigger every morning with an `if-variable` condition on `voltage` — if the latest reading is stale or missing, something upstream is off: breaker, fuse, or the meter itself. A monitor that fails silently is worse than none. Swap Telegram for Slack, Discord, or SMS without touching the conditions — the alert channel is a detail of the automation, not the build. ## What it costs to run Four variables at six readings a minute is roughly a million updates a month. On hosted maker platforms that's deep into paid territory — monthly message caps are exactly what continuous monitoring burns through. Here the meter reports to your own Cloudflare account, where that volume sits comfortably inside normal Workers usage; there is no per-device fee and no message quota to manage. The economics are the point: an energy monitor only earns its keep if it runs continuously for years. ## Going further - **Switch loads from the same board.** A relay on a spare GPIO plus a `NODRIX_WRITE` handler turns the meter into a metered smart switch — the pattern is in [Receive commands on an ESP32](https://nodrix.live/guides/esp32-receive-commands). - **Meter more circuits.** Additional PZEM units share one serial bus with distinct Modbus addresses, so one ESP32 can report `power_lights`, `power_kitchen`, `power_ac` — each auto-creates its own variable and chart series. - **Compute the bill.** Pull the `energy_kwh` series from the read API and multiply by your tariff in a spreadsheet or script — the data is yours, behind one token. - **Track power factor.** The PZEM also reports `pf()` and `frequency()`; two more `Nodrix.send` lines if you want them. ## Notes - **Nothing here can be deprecated on you.** The meter speaks Modbus, the board speaks HTTPS and WebSocket, and the platform is open source (MIT) on your own account. - **The data is queryable.** Every reading lands in your tenancy and comes back out through the read API — no export button to hunt for. - **Configurable without reflashing.** Thresholds, alert channels, and message text all live in the automation editor. ### FAQ **Q: Why a PZEM-004T instead of an SCT-013 clamp and EmonLib?** The PZEM-004T measures voltage, current, power, energy, frequency, and power factor in dedicated metering hardware and hands the ESP32 finished numbers over serial. The SCT-013 route reads a raw current waveform on the ADC and estimates power by assuming a fixed mains voltage, so it drifts with every sag and it can't see power factor. The clamp still has one advantage — it's fully non-invasive — but for numbers you'd bill against, the PZEM is the right tool. **Q: Does this work on both 230V and 110V mains?** Yes. The PZEM-004T v3.0 measures 80–260V AC at 45–65Hz, which covers 110V/60Hz and 230V/50Hz systems alike. The firmware doesn't change — the meter reports whatever it sees. **Q: Where is the kWh total stored, and does it reset when the board reboots?** The energy counter accumulates inside the PZEM itself and survives both ESP32 reboots and power cuts. The firmware just reports it. If you want billing-period totals, keep the lifetime counter as-is and compute deltas in the cloud — or call the library's resetEnergy() from a control write when a new period starts. **Q: Is it safe to build this myself?** The low-voltage side — ESP32, serial wiring, USB — is as safe as any breadboard project. The mains side is not: the PZEM's screw terminals connect to live line and neutral. Work with the circuit de-energized, put everything in an enclosure so no live terminal is exposed, fuse the tap, and if you're not comfortable working inside a mains box, stop at a plug-in smart meter or ask an electrician. The CT coil itself clips around one insulated conductor and never touches copper. **Q: Can the same build switch the load on and off?** Yes — add a relay on a spare GPIO and a NODRIX_WRITE handler for a switch variable, the same downlink pattern as the toggle in the smart-home guide. Size the relay for the load and keep it on the mains side of the enclosure. The meter and the switch stay independent: you can monitor without switching, or switch without trusting the meter. **Q: How much data does reporting every 10 seconds generate?** Four variables every 10 seconds is about 34,000 updates a day. That's nothing for a WebSocket on your own Cloudflare account, but it's exactly the kind of rate that burns through hosted-platform free tiers with monthly message caps — one reason energy monitors are usually the first project to outgrow them. --- ## Guide: Build an ESP32 GPS tracker with a live map — no proprietary cloud URL: https://nodrix.live/guides/esp32-gps-tracker Category: project · Board: ESP32 A complete ESP32 GPS tracker: read a NEO-6M over serial, stream position over one WebSocket, and watch the marker move on a map dashboard you own — no vendor tracking cloud, no per-device fee, on your own Cloudflare account. Most ESP32 GPS tracker tutorials end the same way: your coordinates flow into somebody else's tracking portal, on their account system, at their pleasure. This build keeps the whole path yours. A NEO-6M reads position, the ESP32 streams it over one WebSocket, and the marker moves across a map dashboard served from **your own Cloudflare account** — open source end to end, no tracking vendor, no per-device fee, and a read API if you ever want the raw trail. It's also honest about the one thing GPS tutorials tend to gloss over: GPS tells the board where it is anywhere; reporting it needs an uplink. With Wi-Fi that means a phone hotspot in the vehicle, depot or campus networks for fleet-yard visibility, or your home network for arrive/leave tracking. ## What you'll build - A **live map** with a marker that follows the tracker, updating in place over a WebSocket. - A **speed readout** on the marker and a 24-hour **speed chart**. - A **Telegram alert** when the tracker moves faster than it should. ## What you'll need - An **ESP32** dev board (any common DevKit variant). - A **NEO-6M GPS module** with its ceramic antenna — the ubiquitous blue breakout. - A **power source** where the tracker lives: vehicle USB, a power bank, or a 5V supply. - The **Arduino IDE** with the ESP32 board package, the **Nodrix** library, and the [**TinyGPSPlus**](https://github.com/mikalhart/TinyGPSPlus) library, both from the Library Manager. - A **nodrix instance** with a project and a project token. ## How the GPS side works The NEO-6M speaks NMEA sentences over plain serial at 9600 baud — a stream of text lines carrying position, speed, altitude, and satellite health. TinyGPS++ parses the stream; your sketch just feeds it bytes and asks for numbers. Two field realities to plan around: - **Cold starts are slow.** First fix after power-up takes one to five minutes under open sky while the module downloads orbit data; indoors it may never lock. Once it has run, a warm module re-fixes in seconds. The onboard LED blinks when locked. - **The antenna wants sky.** On a dashboard shelf or rear window, fine; sealed in a metal box, never. Position the ceramic antenna face-up with a view of the sky. ## Wiring Four wires. The NEO-6M runs on 3.3V logic, so it connects to the ESP32 directly: | From | To | Wire | |------|----|------| | NEO-6M VCC | ESP32 3V3 | Power | | NEO-6M GND | ESP32 GND | Ground | | NEO-6M TX | ESP32 GPIO16 (RX2) | Serial | | NEO-6M RX | ESP32 GPIO17 (TX2) | Serial | Some NEO-6M breakouts prefer 5V on VCC (they regulate down); check yours. The TX/RX logic level is 3.3V either way. ## The firmware The sketch has one job per direction: feed NMEA bytes to the parser, and send a position whenever the tracker has actually moved. The [nodrix Arduino library](https://github.com/decoded-cipher/nodrix-sdk) owns the socket and the reconnects. ```cpp #include #include const char* WIFI_SSID = "your-ssid"; // or the phone hotspot in the vehicle const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; TinyGPSPlus gps; double lastLat = 0, lastLng = 0; void setup() { Serial2.begin(9600, SERIAL_8N1, 16, 17); Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } void loop() { Nodrix.run(); while (Serial2.available()) gps.encode(Serial2.read()); if (!gps.location.isValid()) return; static unsigned long lastSend = 0; bool moved = TinyGPSPlus::distanceBetween( gps.location.lat(), gps.location.lng(), lastLat, lastLng) > 15; unsigned long interval = moved ? 5000 : 60000; if (millis() - lastSend >= interval) { lastSend = millis(); lastLat = gps.location.lat(); lastLng = gps.location.lng(); Nodrix.send("lat", gps.location.lat()); Nodrix.send("lng", gps.location.lng()); Nodrix.send("speed_kmh", gps.speed.kmph()); } } ``` Worth understanding rather than copying: - **The send rate follows movement.** Moving, it reports every 5 seconds; parked, once a minute as a heartbeat. The 15-meter threshold is roughly GPS jitter — without it, a parked tracker "drifts" a random walk around the true spot and floods the chart with noise. - **Coordinates stay double-precision.** TinyGPS++ returns doubles and `Nodrix.send` takes them as-is. A 32-bit float holds only about seven significant digits — meters of error at longitude scale — so the sketch never casts. - **No fix, no send.** Until `gps.location.isValid()`, nothing is reported. A map with an honest gap beats a marker confidently parked at 0°N 0°E off the coast of Africa. - **Pin TLS before you ship.** `Nodrix.begin()` connects encrypted but unverified on first run; add `Nodrix.setCACert()` for production, covered in [Connect an ESP32 over HTTPS](https://nodrix.live/guides/esp32-https-cloud). ## Build the dashboard Add a **Map** widget. In its marker settings, add one marker with **source: Lat/Lng variables**, bind `lat` and `lng`, and set the optional value variable to `speed_kmh` so a tap on the marker shows how fast the tracker is moving. Pick a color, choose a basemap, and the marker starts following the board — updates arrive live over the same hibernating WebSocket the dashboards always use. Two more widgets round it out: | Widget | Bind to | Shows | |---|---|---| | Value | `speed_kmh` | current speed | | Chart | `speed_kmh` | the day's movement pattern | The speed chart doubles as a trip log — flat at zero is parked, and each lobe is a drive, wired to nothing but data you already send. ## Add the alert One automation: trigger on a new `speed_kmh` reading, condition **above 90** (pick your number), action: Telegram — "Tracker doing {{value}} km/h." Whether that's a teen driver, a delivery van, or an e-bike that shouldn't be on a highway, the alert channel and threshold are edited in the dashboard, never in firmware. Swap Telegram for Slack, Discord, or SMS without touching the condition. ## Where this design honestly lands - **In-vehicle with a phone hotspot** — full live tracking while you drive. This is the everyday mode, and a hotspot the phone already provides beats a SIM subscription for a personal vehicle. - **Depot, campus, worksite** — assets report whenever they're on site Wi-Fi: live position in the yard, last-known-position the moment they leave. For "which corner of the site is the trailer in," that's the whole job. - **Arrive/leave at home** — on home Wi-Fi the tracker reports approach and departure; the gap in between is the trip. - **Continuous anywhere-tracking** — that's a cellular modem and a monthly SIM, in any product. If the project outgrows Wi-Fi, the dashboard side here doesn't change; only the uplink does. ## Going further - **Battery asset tag.** Swap the always-open socket for wake-fix-report-sleep over HTTP (`Nodrix.beginHTTP()` + `Nodrix.poll()`) and a small cell runs for weeks — the pattern is in [ESP32 battery life](https://nodrix.live/guides/esp32-deep-sleep-battery). Budget for the GPS fix time: keeping the NEO-6M's backup power pin alive makes each wake a hot start. - **A remote "locate now" button.** Add a `NODRIX_WRITE("locate")` handler that forces an immediate send — a dashboard push button gives you on-demand position while parked. - **More trackers, one map.** Each device sends `lat_2`/`lng_2` (or its own token and variables) and gets its own marker on the same map — fleet view is just more markers. - **Pull the trail.** The full position history sits behind the read API — one token gets you the series for any mapping, geofencing, or trip-report post-processing you want to build. ## Notes - **No tracking vendor in the loop.** Position data goes from your board to your Cloudflare account; the map is served from your instance and the history is queryable through your API. - **The moving parts are all replaceable.** NMEA serial in, HTTPS/WebSocket out — swap the GPS module, the board, or even the firmware framework and the dashboard neither knows nor cares. - **Costs track usage.** A tracker at 5-second cadence is well within normal Workers usage on your own account — there's no per-device fee to multiply across a fleet. ### FAQ **Q: Does this track a car anywhere, like a commercial tracker?** Only where it has an uplink. GPS gives the board its position anywhere on earth; getting that position off the board needs Wi-Fi, so live tracking works wherever the tracker can reach a network — a phone hotspot in the vehicle, campus or depot Wi-Fi, or your home network for arrive/leave visibility. Commercial trackers solve this with a cellular modem and a SIM subscription; that's the honest difference, not the GPS part. **Q: Why does my NEO-6M take so long to get a fix?** A cold start legitimately takes one to five minutes with a clear sky view — the module is downloading satellite orbit data. Indoors it may never lock. Give the antenna sky view, keep the module powered so it can hot-start in seconds next time, and watch the module's fix LED: blinking means locked. **Q: Why send latitude and longitude as two variables instead of one?** Because that's what the map widget binds to: a marker follows a lat variable and a lng variable as a pair. Two plain numeric variables also stay useful individually — both are charted, queryable through the read API, and usable in automations without unpacking anything. **Q: Is float precision enough for GPS coordinates?** The firmware sends doubles, so this isn't a worry here — TinyGPS++ hands out double-precision coordinates and Nodrix.send has a double overload. Truncating to 32-bit floats would cost you real accuracy (a float only holds about seven significant digits, which is meters at longitude scale), which is why the sketch never casts. **Q: What happens to the track while the tracker is out of coverage?** The library keeps reconnecting, and positions sent while offline are skipped rather than queued, so the map shows an honest gap and then the marker jumps to the current fix on reconnect. Backfilling the gap would need timestamps attached to old fixes, which telemetry sends don't carry — buffered breadcrumb history is on the roadmap; today the tracker is live-position-first. --- ## Guide: ESP32 notifications: Telegram, Discord, Slack, or SMS from one sketch URL: https://nodrix.live/guides/esp32-notifications Category: project · Board: ESP32 Send alerts from an ESP32 to Telegram, Discord, Slack, SMS, or WhatsApp — without baking bot tokens and webhooks into firmware. The board sends one line of telemetry; the alert logic, credentials, and channel live in the cloud, swappable without reflashing. Search for "ESP32 Telegram notification" and every tutorial hands the board a bot token and an HTTPS client and wishes it luck. It works — until you want the same alert on Discord, or the token leaks with a firmware dump, or you're reflashing a deployed board because the wording changed. This guide inverts it. The ESP32 sends **one line of telemetry** and holds **zero secrets**. The threshold, the message, the credentials, and the channel — Telegram, Discord, Slack, SMS, WhatsApp, email, or PagerDuty — live in an automation on **your own Cloudflare account**, where changing any of them is an edit, not a reflash. One sketch, any channel, swappable forever. The worked example is a freezer monitor — the project where notifications aren't a novelty but the entire point. ## Why the board shouldn't send the alert Sending from firmware means, for every channel: its TLS endpoint compiled in, its credential stored in flash (readable by anyone with the board and five minutes), its message format hard-coded, and its failure modes handled at 3 a.m. by a microcontroller. Multiply by every channel you add. Sending from the cloud means the board's contract is just `temperature` every minute. Then: - **Secrets stay out of flash.** The bot token and webhook URLs live in your nodrix instance, not in something you might one day post to GitHub. - **The channel is a dropdown.** Telegram today, Discord for the household tomorrow, SMS for the cabin with no one watching chat — same firmware, forever. - **The logic is editable.** Threshold, wording, hysteresis, recipients: dashboard edits, applied on the next reading. - **Deep sleep stays deep.** A battery board reports and sleeps; the alerting happens after it's already unconscious. ## What you'll need - An **ESP32** dev board and a temperature sensor — a DS18B20 on a long lead is the classic freezer choice. - The **Arduino IDE** with the ESP32 board package, the **Nodrix** library, and the **DallasTemperature** library, from the Library Manager. - A **nodrix instance** with a project and a project token. - An account on whichever channel should wake you: Telegram, Discord, Slack, or Twilio for SMS and WhatsApp. ## The firmware All of it. Note what's absent: no bot token, no webhook URL, no threshold, no message text. ```cpp #include #include #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; OneWire oneWire(4); DallasTemperature sensors(&oneWire); void setup() { sensors.begin(); Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } void loop() { Nodrix.run(); static unsigned long lastReading = 0; if (millis() - lastReading >= 60000) { lastReading = millis(); sensors.requestTemperatures(); float t = sensors.getTempCByIndex(0); if (t > -100) Nodrix.send("temperature", t); // -127 = sensor disconnected } } ``` The library keeps the socket alive and reconnects on its own; for a battery build, swap `Nodrix.begin` for `Nodrix.beginHTTP` and report once per wake — the alert side of this guide doesn't change at all. ## Wire up a channel Each channel is added once, under your project's integrations, and then reused by any automation. **Telegram** — the best free option for personal alerts: instant, free, works everywhere. Message `@BotFather`, create a bot, paste the **bot token** into the integration; message your bot once, pull your **chat ID** from the `getUpdates` URL, done. Group alerts: add the bot to a group and use the group's chat ID. **Discord** — the right answer when a household or team already lives there. In your server: channel settings → Integrations → Webhooks → copy the **webhook URL**. That URL is the entire credential. The integration can send a plain message or a titled embed — embeds read better for alerts with a value in them. **Slack** — same shape as Discord for workplaces: create an Incoming Webhook in your Slack app settings, paste the URL. If the freezer is in an office, the alert belongs in the channel people actually have open. **SMS and WhatsApp via Twilio** — for alerts that must land when nobody's watching a chat app. Paste your **Account SID**, **auth token**, and sending number. SMS costs real (small) money per message, which the hysteresis below keeps honest; a trial account works for testing but only texts verified numbers. Email and PagerDuty follow the same pattern — a webhook-shaped credential pasted once — and everything below applies to them identically. ## Build the alert One automation: trigger on a new `temperature` reading, condition **above -10**, action: send — "Freezer at {{value}}°C — check the door." The `{{value}}` fills in from the reading that tripped it. That's a working alert. Now make it a good alert: - **Add hysteresis.** A freezer cycling around the line would ping you on every crossing. Alarm above **-10**, and only rearm after the reading recovers below **-15** — the automation's set-a-flag pattern: on alarm, also set an `alerted` variable; condition the alert on `alerted` being off; clear it on recovery. Three conditions in the editor, zero firmware. - **Alert on silence, too.** A dead sensor sends nothing — which no threshold ever catches. Add a schedule trigger (say, every morning) with an `if-variable` check that the last `temperature` is fresh; stale means power, Wi-Fi, or the board itself. A monitor that fails silently is worse than none. - **Escalate by severity.** Above -10: Telegram. Above -5: Telegram and SMS. Two automations, same variable, different thresholds and channels — the melted-food tier earns the message that costs money. ## One sketch, any alert Nothing in this pattern is about freezers. The same firmware shape — read, `Nodrix.send`, repeat — with a different sensor and threshold is a leak detector under the washing machine, a mailbox switch, a greenhouse heat alarm, or the [plant-watering monitor](https://nodrix.live/guides/esp32-automatic-plant-watering) that messages you when the reservoir runs dry. The channel decision stays where it belongs: in a dropdown, six months from now, when you've moved from Telegram to Discord and the board neither knows nor cares. ## Notes - **Credentials live in one place.** Rotating a leaked webhook is one edit in your instance — not a reflash of every deployed board. - **The alert path is yours.** Board → your Cloudflare account → channel API. No third-party automation service in the middle, no monthly task quota. - **Every alert has a paper trail.** The readings that tripped it are in your dashboard's chart and behind the read API — the alert tells you now, the chart tells you why. ### FAQ **Q: Can an ESP32 send a Discord or Slack message directly?** Yes — both are one HTTPS POST to a webhook URL, and plenty of sketches do it. The cost is where the credentials end up: the webhook URL is a secret, and anything baked into firmware can be read back out of flash. It also welds the channel to the board — changing where alerts go, their wording, or their threshold means reflashing. Keeping the send in the cloud fixes all three. **Q: How do I get a Telegram bot token and chat ID?** Message @BotFather on Telegram, send /newbot, and it hands you the bot token. Then message your new bot once (it can't message you first), and fetch https://api.telegram.org/bot/getUpdates in a browser — your chat ID is in the reply. Those two values go into the nodrix Telegram integration, not into the sketch. **Q: Does the SMS route cost money?** Yes — SMS is the one channel here with real per-message cost, via a Twilio account (a trial account works for testing but adds a prefix and only texts verified numbers). That's also why the cloud-side design matters: with thresholds and hysteresis keeping the alert count honest, an SMS bill for a freezer monitor is cents per month, not per day. **Q: Why did I get twenty alerts for one event?** A reading that jitters around the threshold retriggers on every crossing — 29.9, 30.1, 29.8 is three alerts for zero information. Use two levels: alert when the value crosses the alarm line, and only rearm once it recovers past a second line. The dead band between them is hysteresis, it's the same trick a thermostat uses, and it's a condition edit in the automation, not firmware. **Q: Does this work while the ESP32 deep-sleeps?** Yes, and better than on-device sending would. The board wakes, reports one reading over HTTP, and sleeps; the automation evaluates and does any messaging while the board is already unconscious. A battery sensor doesn't stay awake for a TLS handshake with Telegram — it doesn't even know the alert happened. --- ## Guide: MQTT vs HTTP for IoT: an honest comparison for makers URL: https://nodrix.live/guides/mqtt-vs-http-iot Category: concept Most MQTT-vs-HTTP comparisons are written by broker vendors, and it shows. Here's the maker's version: where MQTT genuinely wins, where the famous overhead numbers mislead, why WebSocket is the missing third option, and how to actually choose for an ESP32-class project. Search "MQTT vs HTTP" and nearly everything you'll read was published by a company that sells MQTT brokers. The conclusions follow the incentive: HTTP gets framed as a legacy protocol tolerated only for devices that can't do better, backed by overhead numbers measured under assumptions that flatter the product. MQTT is genuinely excellent at what it was built for — this page gives it full credit — but a maker choosing a protocol for an ESP32 deserves the version without the sales motion. Three claims up front, argued below: the famous efficiency numbers compare an unfairly configured HTTP; the decision for most maker projects is really about whether you want to operate a broker; and the strongest architecture for boards-to-cloud is the one both camps skip — HTTPS up, WebSocket down. ## What each protocol actually is [**MQTT**](https://mqtt.org) is publish/subscribe through a broker. Every device holds a persistent TCP connection to a central broker process; publishers push to topics, subscribers receive from them, and the broker routes. The protocol carries real machinery for unreliable links: three QoS levels, retained messages, and a last-will message the broker emits when a client vanishes. It was designed in 1999 to move pipeline telemetry over satellite links — scarce, expensive bytes — and that heritage is exactly why its framing is so lean. **HTTP** is request/response with no middleman. A device POSTs a reading to an endpoint and gets an acknowledgment in the reply; TLS, load balancing, auth tokens, and debugging tooling come from the web's thirty years of infrastructure. What plain request/response lacks is push: the server can't initiate, so commands wait for the device to ask. **WebSocket** is the third option most comparisons omit: a connection that starts as HTTPS on port 443 and upgrades to a persistent, bidirectional channel. Server push without a broker, firewall-friendly because it is web traffic, one socket carrying telemetry up and commands down. ## The overhead numbers, audited The stock argument says MQTT's per-message overhead is a 2-byte header against HTTP's hundreds of bytes of headers, quoting benchmark figures of roughly 8x the bytes and multiples of the latency. Two things about those numbers: - **They compare a held socket against connection-per-request.** MQTT gets a persistent session; HTTP is made to re-handshake for every message. Real HTTP clients reuse connections — and over a WebSocket, the per-message framing is bytes, not headers. Configured comparably, the protocols converge for the payload sizes makers ship. - **Their provenance is stale.** The most-cited latency figures trace to tests against Google Cloud IoT Core — a service retired in 2023. Numbers that outlive the platform they were measured on are marketing, not engineering. The honest version: on a continuously chatty, always-connected link, MQTT's framing really is leaner, and at fleet scale that compounds. At one reading every ten seconds from a handful of devices, the difference is invisible next to Wi-Fi beacons and TLS record overhead. ## The battery argument, audited "MQTT saves battery" assumes a device that stays connected. Battery devices don't — they sleep. The wake-report-sleep cycle pays for boot, Wi-Fi association, DHCP, and a TLS handshake before the first application byte, whichever protocol follows; then it ships a few hundred bytes and loses power on purpose. A session the device isn't holding can't save it anything. The protocol choice for battery hardware is a rounding error against sleep current and wake frequency — the real levers are in [the deep-sleep guide](https://nodrix.live/guides/esp32-deep-sleep-battery). ## Where MQTT genuinely wins Credit where due — choose MQTT when the shape of your system is its shape: - **Fan-out.** One sensor reading consumed by five services: pub/sub is the right primitive, and a broker does it natively. - **Device-to-device.** Boards talking to boards through topics, no backend in the loop. - **Presence.** Last-will gives you "device went dark" detection at the protocol level. - **Sustained chatter on constrained links.** Always-on cellular devices streaming frequent small messages is the satellite-pipeline problem MQTT was born for. - **Existing broker infrastructure.** A factory with Sparkplug conventions or a team already operating EMQX/Mosquitto — the ecosystem is mature and the marginal cost is paid. ## Where the HTTP family wins - **Nothing to operate.** A broker is a server: something to host, secure, patch, and monitor. Point-to-point HTTPS deletes the component entirely — for a maker, that's usually the whole argument. - **Networks just let it through.** Port 443 web traffic works from campus networks, offices, hotels, and behind corporate proxies where port 1883 is a support ticket. - **The web's toolbox applies.** Bearer tokens, `curl` for debugging, serverless platforms, CDNs, standard load balancing — every piece of web infrastructure is your IoT infrastructure. - **State and history live behind an API,** not in retained topic messages you have to mirror into a database anyway. ## The architecture the comparisons skip The classic knock on HTTP for IoT is the downlink: "how does the server tell the device anything?" Polling is the crude answer; a WebSocket is the good one. The pattern that gets the best of both camps, and the one nodrix is built around: - **Telemetry up** as plain HTTPS or over the socket — stateless, debuggable, serverless-friendly. - **Commands down** the same WebSocket — real push, at-least-once delivery, no broker, no polling. - **Battery devices** drop the socket entirely: wake, POST the reading, collect any pending command in the response, sleep — [the downlink guide](https://nodrix.live/guides/esp32-receive-commands) shows both modes behind one handler. That's not a compromise position between MQTT and HTTP; for boards-talking-to-your-backend, it's simply the fit. The broker earns its keep when messages have many consumers or device peers. When every message has exactly one destination — your platform — the broker is a mandatory middleman for a conversation with two parties. ## Choosing, compressed | Your system looks like | Use | |---|---| | Boards report to your backend; occasional commands back | HTTPS + WebSocket | | Battery sensors that sleep between reports | HTTPS, poll-on-wake | | One stream, many independent consumers | MQTT | | Devices messaging each other directly | MQTT | | Existing broker/Sparkplug infrastructure | MQTT | | Hostile networks (campus, corporate, hotel) | HTTPS + WebSocket | If your project is in the first two rows — and most maker projects are — the practical next step is seeing the broker-free version running: [an ESP32 on HTTPS](https://nodrix.live/guides/esp32-https-cloud) with [commands coming down the socket](https://nodrix.live/guides/esp32-receive-commands), on an instance you [deploy to your own Cloudflare account](https://nodrix.live/guides/deploy-nodrix-cloudflare) in one click. The best protocol argument is a working dashboard with nothing else to run. ### FAQ **Q: Is MQTT faster and lighter than HTTP?** Per message on an open connection, yes — MQTT's fixed header is 2 bytes and HTTP's headers are hundreds. But the comparisons that turn this into 'HTTP is 8x heavier' assume a new HTTP connection per message while MQTT keeps its socket open — an unfair matchup. Give HTTP the same courtesy (keep-alive, or a WebSocket) and the per-message gap shrinks to noise for typical maker payloads. For a duty-cycled sensor, the TLS handshake on wake dominates either protocol equally. **Q: Do I need MQTT for an ESP32 project?** Only if you need what a broker uniquely provides: fan-out of one message to many subscribers, device-to-device messaging, or presence via last-will. A board that reports readings to one backend and receives occasional commands — which is most maker projects — is a point-to-point conversation that HTTPS up and a WebSocket down handles with one less system to run. **Q: Does HTTP drain more battery than MQTT?** Not in the sleep-most-of-the-time pattern that battery projects actually use. A sensor that wakes every 15 minutes pays for boot, Wi-Fi association, and a TLS handshake before either protocol says a word — that's the budget, and it's identical. MQTT's efficiency accrues to devices that stay awake and chat continuously; a device that sleeps can't benefit from a session it isn't holding. **Q: Why do most IoT platforms push MQTT?** Partly genuine merit at fleet scale — and partly because many of them are broker companies, so the comparison you're reading is often the sales page. Note what their own numbers lean on: the widely-quoted latency and overhead benchmarks trace back to tests against a cloud IoT service that was retired in 2023, under connection-per-request assumptions. Read protocol advice the way you'd read a benchmark from anyone selling one side of it. **Q: What about CoAP, AMQP, or LoRaWAN?** Different problems. CoAP targets networks too constrained for TCP (it runs over UDP); AMQP is enterprise message-queue territory; LoRaWAN is a radio network whose gateways typically hand data to a backend over IP anyway. For Wi-Fi-class maker hardware — ESP32, ESP8266, Pico W — the real decision is the one on this page. --- ## Guide: nodrix vs Grafana + InfluxDB: the DIY IoT stack, weighed honestly URL: https://nodrix.live/guides/nodrix-vs-grafana-influxdb Category: comparison Grafana with InfluxDB is the forum-default answer for IoT dashboards — and it's half an answer. Here's the honest comparison: what the DIY stack does better, what it quietly doesn't do at all, and what four self-hosted services really cost against one serverless deploy. Ask a forum how to dashboard your ESP32's data and the reflex answer is Grafana with InfluxDB. It's a good reflex — both are excellent, open-source, battle-hardened tools. It's also half an answer: neither speaks to a device, and the missing half is where the real work and the real maintenance live. This page weighs the whole thing honestly — including the cases where the DIY stack is exactly what you should build. nodrix's position in the comparison: the four capabilities the stack assembles — ingest, storage, display, alerts — plus the one it can't (device control), in a single open-source deploy on **your own Cloudflare account**, with nothing to operate. ## What "Grafana + InfluxDB" actually means Grafana charts what lands in a database. InfluxDB is the database. Neither includes a path from a microcontroller, so the real deployment is a stack: 1. **An MQTT broker** (usually Mosquitto) for the boards to publish to, 2. **A collector** (Telegraf, Node-RED, or a hand-written bridge) moving broker → database, 3. **InfluxDB** storing the series, 4. **Grafana** on top — plus a host for all four, TLS in front of them, updates, and backups. Each piece is great. The sum is a distributed system you now administer, and its integration points — topic naming, retention policies, datasource auth — are yours to design and to debug at each version bump. ## Stack vs nodrix, honestly | | Grafana + InfluxDB (+ broker + collector) | nodrix | |---|---|---| | Visualization depth | Exceptional — plugins, multi-source, transformations | Purpose-built IoT widgets | | Query power | Flux/InfluxQL/SQL, full analytics | Read API: state + time-series | | Device ingest | You assemble (broker + bridge) | Built in: HTTPS/WebSocket + Arduino library | | Device control (downlink) | Not offered — build your own | Built in: toggle/slider widgets → `NODRIX_WRITE` | | Alerting | Grafana Alerting (strong, data-side) | Trigger → condition → action, device-aware | | Services to operate | Four, plus host, TLS, backups | Zero — serverless on your Cloudflare account | | Cost shape | VPS or home server + your hours | Cloudflare usage; hobby scale typically free | | Open source | Yes (per component) | Yes (MIT, one stack) | ## When the DIY stack is the right call - **The data already lives in databases.** Grafana across your Postgres, Prometheus, and Influx instances is its home game; no IoT platform touches it there. - **You need real query power** — window functions, joins across sources, transformations. If your project is analysis, Flux and SQL beat any widget config. - **You already run the infrastructure.** A homelab with Mosquitto and Influx humming has paid the ops cost; adding one more dashboard is nearly free. - **Visualization is the product.** For wall-mounted, deeply customized displays, Grafana's plugin ecosystem is unmatched. ## When nodrix fits better - **Devices are the point.** Boards connect with a few lines — `Nodrix.send` up, `NODRIX_WRITE` down — with no broker, no topic scheme, no bridge to write. - **You want control, not just charts.** A toggle on the dashboard flips the relay. In the DIY stack that feature simply does not exist until you build it. - **Zero ops is the feature.** One click deploys to your Cloudflare account; there is no VM to patch, no broker to restart, no backup cron. The stack's four services are four things that can page you. - **Alerts should know about devices.** "When `temperature` crosses 30, Telegram me" is one automation — not a query, a rule, a contact point, and a notification policy. ## The cost accounting people skip The DIY stack's software is free; the system costs a server (a VPS bill or a home machine's power and presence) and, more honestly, your hours: version bumps across four components, certificate renewals, the broker that stopped after a power cut, the disk Influx filled. None of it is hard; all of it recurs, whether or not the project still excites you. The serverless trade is exactly that line item deleted: nodrix runs on Cloudflare's infrastructure under your account, sized so hobby telemetry sits in the free plan. The trade-back is flexibility — you can't ssh into it, tune retention policies, or bolt arbitrary plugins onto it. That's the honest shape of the choice: **their power, your hours** versus **fewer knobs, zero hours**. ## Split it: devices here, analysis there The two aren't exclusive. A clean architecture for heavy-analysis projects: nodrix owns the device layer — ingest, live dashboard, control, alerts — and everything it stores stays reachable through the read API (one token, plain JSON: current state and time-series). Grafana, a notebook, or a script pulls from that API when you want the deep dive. You run zero device infrastructure and still get the query power on demand. ## The bottom line If your project is fundamentally about querying and visualizing data you already have, build the Grafana stack — it's the best there is at that. If your project is about *hardware you want to see and control from anywhere*, the DIY stack hands you a systems-administration hobby on top of your electronics hobby. [Deploy nodrix](https://nodrix.live/guides/deploy-nodrix-cloudflare), point [one board at it](https://nodrix.live/guides/esp32-https-cloud), and keep the soldering iron as the only thing you maintain. ### FAQ **Q: Can Grafana show live IoT sensor data?** Yes — once the data is in a database it can query. That 'once' is the whole comparison: Grafana has no device-facing ingest and no device protocol, so an IoT setup needs something between the board and the database, usually an MQTT broker plus Telegraf or a custom bridge. Grafana is the display layer of a stack, not the stack. **Q: Can I control a device from a Grafana dashboard?** Not in any built-in way — Grafana is read-only by design. It visualizes and alerts on data; it has no concept of writing state back to a device. The day your project wants a toggle that flips a relay, you're building an ingest-plus-command service yourself. Downlink is the sharpest single difference in this comparison: in nodrix a toggle widget writes a variable and the board's handler fires. **Q: Isn't the DIY stack more powerful than nodrix?** At visualization and querying — genuinely yes. Grafana's plugin ecosystem, multi-source dashboards, and query languages are in a different league than any IoT platform's built-in charts, nodrix included. The question is whether your project needs that power, and needs it enough to operate a broker, a database, a collector, and Grafana itself. Most maker telemetry is a dozen series and a threshold alert — the power goes unused while the maintenance doesn't. **Q: Can I use Grafana with nodrix instead of choosing?** Yes, and it's a sensible split for heavy analysis: nodrix handles devices, live dashboards, control, and alerts, while every reading stays queryable behind the read API — one token, JSON out. Point any external tool at it, Grafana included via a JSON API datasource. You keep the zero-ops device layer and borrow Grafana's query power when you actually need it. --- ## Guide: Open-source IoT dashboards that actually work for makers URL: https://nodrix.live/guides/open-source-iot-dashboard Category: concept What a maker's IoT dashboard actually needs, which open-source options deliver it, what each costs to run, and when a serverless deploy wins. Search for "open-source IoT dashboard" and the results are written for someone else. The typical list compares industrial middleware — rule engines, OPC-UA gateways, Kubernetes charts — and never mentions a microcontroller. Some still recommend tools that have been dormant for years. If what you actually have is an ESP32 and a sensor, you're left translating factory-floor comparisons into maker terms. This page is that translation. It lays out what an IoT dashboard genuinely has to do, then walks through the open-source options a maker would actually shortlist — including where each one is the wrong answer. ## What an IoT dashboard actually has to do "Dashboard" undersells the job. Four capabilities separate a real IoT platform from a chart on a web page: - **Ingest** — an open protocol a $5 board can speak directly (HTTP, WebSocket, or MQTT), with authentication that isn't a cloud SDK. - **Live display** — gauges, charts, maps that update as data arrives, not on refresh. - **Downlink** — a way to send state back: a toggle on the dashboard that flips a relay. This is the one most "dashboard" tools silently lack. - **Logic** — thresholds, schedules, alerts routed to where you actually look (Telegram, Slack, Discord, email), editable without redeploying anything. Hold every candidate against those four and the crowded field thins fast. ## The DIY stack: Grafana + InfluxDB (+ broker + glue) The forum-default answer. Grafana is a world-class visualization layer and InfluxDB a fine time-series store — and together they cover exactly half the job. There's no device protocol (you'll add an MQTT broker and a bridge, or write an ingest service), and no downlink at all: Grafana is read-only by design, so the day you want a toggle, you're building a control plane from scratch. Even Grafana's own ecosystem concedes the setup-and-maintenance burden is the tax. **Right when:** visualization is the entire requirement, data already lands in a database, and you enjoy running the stack. **Wrong when:** you want to control anything, or when four services to patch is four more than the project deserves. This stack gets [its own full weighing](https://nodrix.live/guides/nodrix-vs-grafana-influxdb). ## The self-host heavyweight: ThingsBoard CE The most complete open-source IoT platform, full stop — device management, a rule engine, dashboards, multi-tenancy. The cost is operational: it's a Java application with PostgreSQL and a message broker underneath, which means a real server, real RAM, and someone (you) patching it. Note that its hosted cloud has no free tier, so "free ThingsBoard" specifically means self-hosting. **Right when:** you have a home server and industrial-grade requirements. **Wrong when:** the platform would be the heaviest thing in the project — [the full comparison](https://nodrix.live/guides/thingsboard-alternative) is the honest version of this trade. ## The flow-wiring toolkit: Node-RED Node-RED is a joy for wiring logic — but it's a component, not a platform. Out of the box there's no data store, no device registry, and the dashboard is an add-on; the usual deployment pairs it with a broker, a database, and Grafana, at which point you're operating the DIY stack with a nicer editor. It shines as glue around a platform rather than as the platform. ## The different animal: Home Assistant Home Assistant is magnificent — at its actual job, which is being a local hub for off-the-shelf smart-home gear. It's not built to be a cloud telemetry backend for custom hardware: remote access means a subscription or reverse-proxy work, and its data model orbits home automation, not fleets of sensors. [Home Assistant vs nodrix](https://nodrix.live/guides/home-assistant-vs-nodrix) draws the line properly — plenty of people correctly run both. ## The zombie recommendations A surprising share of "best open-source dashboard" content still lists projects that stopped moving years ago — Freeboard is the recurring example: dormant repo, hosted service gone, still ranked in roundups. The tell for this whole category: check the commit history and the issue tracker before you check the feature list. An unmaintained dashboard is a security liability with widgets. ## The serverless option: nodrix nodrix is our entry in this field, and its position is specific: all four capabilities in one deploy, with **no server to operate**. It's open source (MIT) and deploys in one click to your own Cloudflare account — Workers, Durable Objects, D1 — so there's no VPS, no Docker Compose, no patching schedule. Devices speak plain HTTPS/WebSocket (an Arduino library covers ESP32/ESP8266; anything else can use raw HTTP), dashboards are drag-and-drop with live widgets including toggles and sliders that write back to the board, and automations route alerts to Telegram, Slack, Discord, email, SMS, or any webhook. Data stays in your tenancy behind a read API. The honest trade: it's serverless-shaped. If you need MQTT specifically, an on-LAN-only system, or industrial protocol gateways, the heavyweights above earn their weight. For the actual maker case — custom boards, live dashboards, control, alerts, zero ops — the serverless shape is the point. ## The decision, compressed | You want | Pick | |---|---| | Charts over an existing database, read-only | Grafana (+ InfluxDB) | | Industrial feature set, own server, ops appetite | ThingsBoard CE | | Visual glue logic around another platform | Node-RED | | A local hub for store-bought smart-home devices | Home Assistant | | Device-to-dashboard-to-device, zero servers | [nodrix](https://nodrix.live/guides/deploy-nodrix-cloudflare) | One rule outranks the table: prefer the tool whose whole stack you're willing to operate. Every open-source IoT dashboard is free software; the difference between them is how much infrastructure each one quietly asks you to own. Picking the platform is really picking your ops burden — choose the one whose answer is a number you'll still accept in a year. ### FAQ **Q: What's the best open-source IoT dashboard for an ESP32 project?** For a maker project, the shortlist is honest: nodrix if you want dashboards, device protocol, and automations in one deploy with nothing to operate; ThingsBoard CE if you have a server and want the industrial feature set; Grafana with InfluxDB if visualization is the whole requirement and you don't need to control devices. The rest of the tools on typical 'best of' lists are either industrial middleware or business-metrics dashboards that never met a microcontroller. **Q: Is Grafana an IoT dashboard?** Grafana is a superb visualization layer, but it only displays: it charts what lands in a database, and it has no device protocol, no way to send a command back to a board, and no concept of a device at all. An IoT dashboard needs an ingest path and a downlink. Grafana can be the display half of a DIY stack — it just can't be the whole thing. **Q: Can I self-host an IoT platform for free?** The software is free; the operating isn't. A self-hosted stack costs a VPS or home server, plus the recurring patching, backups, and TLS renewals that keep it healthy. The serverless variant moves that cost to effectively zero for hobby scale: deploy once to your own Cloudflare account and there's no machine to maintain. **Q: Whatever happened to Freeboard?** Freeboard still gets recommended by older 'open-source dashboard' roundups, but the project has been effectively unmaintained for years — the repo is dormant and the hosted service is gone. It's the clearest sign a list you're reading wasn't written by anyone who tried the tools: check the commit history before adopting anything in this space. --- ## Guide: A Particle alternative you own — open-source, on your Cloudflare account URL: https://nodrix.live/guides/particle-alternative Category: comparison Particle was acquired by Digi International in January 2026 and is being folded into an OEM business. For Wi-Fi makers weighing their options, nodrix is an open-source alternative you deploy to your own Cloudflare account — no subscription, no vendor cloud, plain HTTPS/WebSocket. In January 2026, Digi International acquired Particle for $50 million and began folding it into its OEM Solutions business. The platform keeps running, but the direction is now set by enterprise device programs, not the community Particle itself puts at a quarter-million developers — the people who grew up on Spark Cores, Photons, and Argons. If you're one of them and reassessing, the honest first question isn't "what replaces Particle" — it's which Particle you were using. nodrix replaces one of them well: the Wi-Fi one. It's open source (MIT), you **deploy it to your own Cloudflare account** in one click, and devices talk plain HTTPS/WebSocket — telemetry up, control writes down, dashboards, automations, and a read API, all in your tenancy with no vendor cloud to be acquired out from under you. ## The two Particles Particle is really two products under one SDK: - **A managed cellular fleet platform** — SIMs and carrier relationships across hundreds of networks, Device OS, OTA updates, fleet health. This is what Digi bought, it's genuinely good, and there is no drop-in open-source substitute. If your devices live on cellular in the field, nodrix is not your answer, and this page won't pretend otherwise. - **A friendly Wi-Fi prototyping cloud** — `Particle.publish` from a Photon or Argon on your bench, a console to watch events, functions you call remotely, webhooks into the rest of the internet. This is how most makers actually used Particle, and it's the part that maps cleanly onto nodrix. ## Particle vs nodrix, honestly | | Particle | nodrix | |---|---|---| | Model | Commercial platform, subscription | Open-source (MIT); you deploy it to your own Cloudflare | | Owned by | Digi International (since Jan 2026) | You — it runs in your account | | Where data lives | Particle's cloud | Your Cloudflare account (single-tenant) | | Connectivity | Cellular (managed SIMs) + Wi-Fi | Any board that speaks HTTPS/WebSocket | | Hardware | Particle boards + Device OS | Bring your own — ESP32, ESP8266, Pico W, anything | | Uplink | `Particle.publish` events | `Nodrix.send` telemetry (auto-creates variables) | | Downlink | `Particle.function`, variables | `NODRIX_WRITE` handlers over WebSocket or polling | | OTA updates | First-class, fleet-wide | On the roadmap; cloud-side logic changes need no reflash | | Dashboards | Console; build your own UI | Drag-and-drop web dashboards, embeddable widgets | | Automations | Webhooks, integrations | Visual trigger → condition → action, run at the edge | | Pricing | Subscription tiers | No license; Cloudflare usage (hobby scale: free plan) | ## When Particle is still the better choice - Your devices are on **cellular**. Managed SIMs, carrier fallback, and fleet connectivity are the hard part of that world, and Particle does it properly. - You depend on **fleet OTA** — staged firmware rollouts across hundreds of field units. - You're an **OEM building a product line** and the Digi direction is a feature for you, not a concern: enterprise support contracts and a hardware-to-cloud vendor relationship. ## When nodrix fits better - Your boards are on **Wi-Fi** — the bench, the house, the workshop, the campus — and the cellular machinery was never the part you used. - You want **out of the acquisition business entirely**: the stack is MIT-licensed and runs in your own Cloudflare account, so there is no platform owner whose strategy can change your project. - You want **costs that track usage, not seats or devices** — no subscription, no per-device fee. - You'd rather have **dashboards and automations included** than build a UI against a console. ## Moving a project across The firmware shape survives the move — Particle's model of events up, functions down maps directly: ```cpp #include NODRIX_WRITE("led") { // was: Particle.function("led", ...) digitalWrite(LED_PIN, value.asBool()); } void setup() { pinMode(LED_PIN, OUTPUT); Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } void loop() { Nodrix.run(); static unsigned long last = 0; if (millis() - last >= 30000) { last = millis(); Nodrix.send("temperature", readTemp()); // was: Particle.publish("temperature", ...) } } ``` The hardware usually moves too: Particle boards are built around Device OS and the Particle cloud, so the pragmatic path is a standard ESP32-class board — a few dollars, the same Arduino toolchain, and no platform assumptions baked into the silicon. The full firmware walkthrough, TLS pinning included, is in [Connect an ESP32 over HTTPS](https://nodrix.live/guides/esp32-https-cloud). Webhook-style glue — "when this event, call that service" — recreates as trigger-condition-action automations with Slack, Discord, Telegram, email, or plain HTTP actions. ## The bottom line If Particle's cellular fleet machinery is your product's backbone, stay and watch how the Digi integration lands. If Particle was your friendly Wi-Fi cloud, the acquisition is a good moment to own the stack instead: deploy nodrix to your Cloudflare account, point one ESP32 at it, and the part of Particle you actually used is yours now, in your own account. ### FAQ **Q: What happened to Particle?** Digi International acquired Particle in January 2026 for $50 million and is integrating it into its OEM Solutions business. Particle's platform continues to operate — this is a change of ownership and direction, not a shutdown — but the center of gravity is now enterprise and OEM device programs rather than the maker community Particle grew up with. **Q: Is there an open-source alternative to Particle?** For the Wi-Fi side of what Particle does — telemetry, remote control, dashboards, webhooks-style automations — yes: nodrix is open source (MIT) and deploys to your own Cloudflare account, so there's no vendor cloud in the loop at all. There is no like-for-like open-source replacement for Particle's managed cellular connectivity; that part of Particle is genuinely hard to substitute. **Q: Can I keep using my Particle hardware with nodrix?** Particle boards are engineered around Device OS and the Particle cloud, and that pairing is most of their value. The practical migration is to move the project, not the board: standard ESP32-class hardware costs a few dollars, and the firmware shape carries over — Particle.publish becomes Nodrix.send, and a Particle.function becomes a NODRIX_WRITE handler. **Q: Does nodrix do over-the-air firmware updates like Particle?** Not today — OTA updates are on the roadmap. Particle's OTA and fleet tooling are first class, and if you're updating firmware across a deployed fleet regularly, that's a real reason to stay. nodrix's design reduces the pressure somewhat: dashboards, automations, thresholds, and alert channels all live in the cloud and change without reflashing, so the firmware itself can stay a thin, stable contract. **Q: What does nodrix cost compared to a Particle plan?** nodrix has no license or subscription — you deploy it to your own Cloudflare account and pay Cloudflare for usage, which for hobby and small-fleet Wi-Fi workloads typically sits within the free plan. Particle is a commercial subscription platform; you're paying for managed connectivity, OTA, and fleet tooling. Which is 'cheaper' depends entirely on whether you need those managed services. --- ## Guide: How to update a nodrix instance: one rebuild, nothing to migrate URL: https://nodrix.live/guides/update-nodrix Category: concept Updating a self-hosted nodrix deployment is one button, not a migration project: the app tells you when a release is out, one rebuild pulls it in, and schema changes apply themselves. Here's exactly how the update path works, how to check your version, and how to roll back. The quiet fear of self-hosting is the second year: the update you postpone because it might mean migrations, config drift, or an evening of reading upgrade notes. nodrix's update path is built to delete that fear. Your instance tells you when a release is out, one rebuild brings it in, schema changes apply themselves, and your data never enters the blast radius. This guide covers the whole loop: how your deployment relates to the upstream project, how to know an update exists, how to apply it, and what to do in the rare case one misbehaves. ## How your deployment actually relates to upstream When you used the Deploy to Cloudflare button, it didn't fork the nodrix codebase into your GitHub account. It created a small **deploy carrier** — a repo holding little more than your `wrangler.toml`, the file with your D1, R2, KV, and Durable Object resource IDs. The code isn't in there at all. Instead, every time Cloudflare's Workers Builds runs a build for your instance, the build step pulls the **latest published nodrix release** from the upstream repository, lays it over the carrier, preserves your `wrangler.toml`, and builds that. Your clone is configuration; the code is always a released version of upstream. Two properties fall out of this design, and they're the whole update story: - **Updating is just rebuilding.** There is no fork to sync, no upstream remote to merge, no conflict to resolve. Any new build of your Worker is, by construction, the newest release. - **Nothing updates without you.** A release on our side changes nothing in your account. Your instance runs exactly what it ran until you trigger a build — the update is always your action, on your schedule. ## Knowing an update exists You have three signals, in decreasing order of convenience: - **The app tells you.** Open **Settings** in your instance: the **Version & updates** panel shows the running version, the commit and build time behind it, and checks upstream for the latest published release. If there's something newer, it says **Update available** and shows you what it is. The check is polite by design — results are cached in your instance's KV for a few minutes and revalidated conditionally, so it stays well inside GitHub's rate limits without any token. - **Watch the repository.** On GitHub, watch the nodrix repo with **Watch → Custom → Releases** and you'll get a notification (or an entry in the releases Atom feed, if you're an RSS person) the moment a version is published — no code noise, releases only. - **Check the changelog.** [The changelog](https://nodrix.live/changelog) lists every release with what changed. Worth a skim before updating anyway: it's how you find out an update includes something you've been waiting for. Releases are versioned semantically — features bump the minor version, fixes bump the patch — so the version string itself tells you roughly how much changed. ## Applying the update From the **Version & updates** panel, the update button takes you straight to your Worker's **Builds** page in the Cloudflare dashboard — sign in, hit **Retry build** on the latest build, and Cloudflare rebuilds your instance. The rebuild pulls the newest release, and a few minutes later the deploy goes live. Back in Settings, your instance rechecks itself and the panel flips from **Update available** to **Up to date** on its own — if it's still showing stale after the build finishes, that's your cue to look at the build log. That's the entire procedure for a button-deployed instance: no terminal, no git, no downtime beyond the atomic swap of Worker versions. If you deployed manually from a full clone instead — some people prefer owning the whole pipeline — updating is the git-native version of the same thing: ```bash git pull # bring your clone up to the release you want bun install bun run deploy:platform # build + deploy the worker with your wrangler.toml ``` Same result, different trigger. Everything else in this guide — migrations, data safety, rollback — applies identically. ## What happens during an update Knowing the anatomy makes the button easier to trust: - **Your configuration survives.** The build preserves your `wrangler.toml` — the resource IDs written on day one — before overlaying the new source, and restores it after. Your bindings never drift. - **Schema changes apply themselves.** Database migrations ship bundled inside the Worker, and an auto-migrator applies any pending ones at runtime. There's no `wrangler d1 migrations` step, no ordering to think about, and migrations are additive — they extend the schema rather than destroying data. - **Your data isn't part of the build.** Telemetry, dashboards, users, tokens, automations — all of it lives in D1, R2, and Durable Object storage in your account. A build produces a new Worker and web bundle; it doesn't read or write any of that. - **The swap is atomic.** Cloudflare cuts traffic over to the new Worker version when the deploy completes. Devices reconnect their WebSockets automatically — the same reconnect logic they use for any network blip — and HTTP-polling devices never notice at all. ## If an update misbehaves The honest section, because "rarely" isn't "never": - **Roll back from Cloudflare.** Your Worker's deployment history lives in the dashboard; rolling back to the previous deployment restores the prior version in one action. Because migrations are additive, the older Worker runs fine against the newer schema. - **Read the build log.** A failed build never replaces your running instance — the old version keeps serving. The log on the Builds page says what went wrong, and retrying after a transient failure (a network hiccup during the upstream fetch, say) usually resolves it. - **Check the release notes.** If a release ever needs something from you — which the design works hard to avoid — its notes on the releases page and [the changelog](https://nodrix.live/changelog) are where that would be said plainly. ## The cadence worth adopting There's no forced pace — an instance that runs untouched for six months keeps working, and the update prompt just waits. But the design rewards a simple habit: when Settings says **Update available**, take the two minutes. Small, frequent updates mean each one carries little change, release notes stay skimmable, and a rollback — should you ever need one — steps back over one release instead of ten. The update path was built to be boring; using it often keeps it that way. ### FAQ **Q: Will updating nodrix wipe my dashboards or telemetry?** No. An update replaces only the code — the Worker and the web app. Your data lives in your account's D1 database, R2 bucket, and Durable Objects, none of which a rebuild touches, and your wrangler.toml with its resource IDs is preserved across every build by design. Schema changes ship inside the new Worker and apply themselves without dropping data. **Q: How do I check which version my instance is running?** Open Settings inside your instance — the Version & updates panel shows the running version, the commit it was built from, when it was built, and whether the upstream project has published something newer. It's the same check the update prompt uses, so what you see there is the answer. **Q: Do I need to run database migrations when I update?** No. Migrations are bundled into the Worker at build time and a runtime auto-migrator applies any pending ones — there's no wrangler command to run and no step to forget. This is deliberate: an update path with a manual migration step is an update path people skip. **Q: Why is my instance still on the old version right after a release?** Because deployments pull the latest release at build time, not continuously — a new release doesn't push itself into your account. Your instance keeps running exactly what it has until you trigger a rebuild, which then picks up the newest published release. That's a feature: nothing about your deployment changes without you pressing the button. **Q: Can I roll back if an update misbehaves?** Yes, from Cloudflare's side: the Worker keeps its deployment history in the dashboard, and you can roll back to the previous deployment there. Schema migrations are additive, so a rolled-back Worker runs happily against the newer schema. If you ever hit a release that needs more than that, its release notes will say so. --- ## Guide: Build a DIY smart home with an ESP32 and your own cloud URL: https://nodrix.live/guides/esp32-smart-home-automation Category: project · Board: ESP32 A complete ESP32 smart-home build: switch lights and appliances through relays, control them from anywhere on one private dashboard, and let scenes, schedules, and a sunset trigger run the house — no hub, no broker, on your own Cloudflare account. A smart home is really two things: switches you can throw from anywhere, and rules that throw them for you. The usual DIY route bolts those onto a hub you keep alive in a closet and a broker you have to secure. This build skips both. An ESP32 switches the lights and appliances through relays, and every decision — the dashboard, the scenes, the schedules, the sunset trigger — lives in nodrix on **your own Cloudflare account**. The board's whole job is to flip relays when it's told to and report what state they're in. Nothing about the house is compiled into it, so you add a room, retime the porch light, or build a "Goodnight" scene from the dashboard, and the firmware you flashed once never changes. ## The idea: your house, your cloud, no hub Put the automation logic on the ESP32 and every change means reflashing, every rule is invisible, and the board can't tell you a relay stuck on. Split it the other way: - **The device switches and reports** — it listens for `light_living`, `light_bedroom`, `light_porch`, and `fan`, and echoes each relay's real state back. That contract almost never changes. - **The cloud holds the logic** — scenes, schedules, the sunset trigger, and any condition are edited in nodrix and take effect immediately, no reflash. - **The cloud holds the controls** — one dashboard drives every relay from any phone or laptop, on the home network or off it. ## What you'll build - A **dashboard** with a toggle per light and appliance, reachable from anywhere. - A **porch light that follows the sun** — on at dusk, off in the morning, no timer to reset. - A **Goodnight scene** that turns the house off in one tap, and again on a schedule. - **State that stays honest** — each toggle reflects the relay's actual position, even after a reboot or a reconnect. ## What you'll need - An **ESP32** dev board (any common DevKit variant). - A **relay module** — a 4- or 8-channel, **opto-isolated** board. Match the channel count to how many circuits you're switching. - The loads: **light fixtures, LED strips, a fan, or smart plugs**. Prefer low-voltage loads while you're learning; leave mains wiring to a qualified electrician. - Jumper wires, and a **5V supply** for the relay board if it draws more than the ESP32 can source. - The **Arduino IDE** with the ESP32 board package and the **Nodrix** library from the Library Manager (it pulls in ArduinoJson and WebSockets). - A **nodrix instance** with a project and a project token. ## Wiring the relays Each relay is switched by one GPIO. Wire four control lines from the ESP32 to the relay board's inputs, power the board, and share a ground: | From | To | Wire | |------|----|------| | ESP32 GPIO16 | Relay IN1 | Signal (living) | | ESP32 GPIO17 | Relay IN2 | Signal (bedroom) | | ESP32 GPIO18 | Relay IN3 | Signal (porch) | | ESP32 GPIO19 | Relay IN4 | Signal (fan) | | ESP32 5V (VIN) | Relay VCC | Power | | ESP32 GND | Relay GND | Ground | Each relay's **COM** and **NO** terminals go in series with the load's supply — the relay is just a switch in that circuit. GPIO16–19 are safe general-purpose outputs; avoid the strapping pins (GPIO0, 2, 12, 15) for relay control so a relay's power-on state can't hold the ESP32 in boot mode. **Mains voltage is dangerous.** Anything switching household AC must use a properly rated, opto-isolated relay with the mains side fully enclosed and isolated from the ESP32's low-voltage wiring — and in many places that wiring must be done by a licensed electrician. If any of that is uncertain, switch **low-voltage LED strips or a smart plug** instead. The firmware doesn't care what the relay switches. ## The firmware One socket carries the whole house: toggle states come down it the instant you tap the dashboard or a scene fires, and each relay's real state goes back up so the controls never lie. The [nodrix Arduino library](https://github.com/decoded-cipher/nodrix-sdk) holds that socket, acks each command, and reconnects on its own, so the sketch is just four relays and their handlers — the complete version is the [HomeLights example](https://github.com/decoded-cipher/nodrix-sdk/tree/master/examples/HomeLights). ```cpp #include #define WIFI_SSID "your-wifi" #define WIFI_PASS "your-password" #define HOST "nodrix.you.workers.dev" #define TOKEN "tok_your_project_token" const int LIGHT_LIVING = 16; const int LIGHT_BEDROOM = 17; const int LIGHT_PORCH = 18; const int FAN = 19; const int RELAY_ON = LOW; // most relay boards are active-LOW — swap if yours isn't const int RELAY_OFF = HIGH; void setRelay(int pin, const char* var, bool on) { digitalWrite(pin, on ? RELAY_ON : RELAY_OFF); Nodrix.send(var, on); // echo the real state so the dashboard stays honest } NODRIX_WRITE("light_living") { setRelay(LIGHT_LIVING, "light_living", value.asBool()); } NODRIX_WRITE("light_bedroom") { setRelay(LIGHT_BEDROOM, "light_bedroom", value.asBool()); } NODRIX_WRITE("light_porch") { setRelay(LIGHT_PORCH, "light_porch", value.asBool()); } NODRIX_WRITE("fan") { setRelay(FAN, "fan", value.asBool()); } void setup() { int pins[] = { LIGHT_LIVING, LIGHT_BEDROOM, LIGHT_PORCH, FAN }; for (int p : pins) { pinMode(p, OUTPUT); digitalWrite(p, RELAY_OFF); } Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } void loop() { Nodrix.run(); } ``` Worth understanding rather than copying: - **The echo keeps controls honest.** Reporting state back inside each handler means a toggle shows the relay's true position — a scene, a schedule, or a manual tap can't leave the dashboard out of sync with the wall. Because a handler just sets a pin, a duplicate delivery across a reconnect is harmless, and the library re-applies the last known states so the house comes back the way you left it after a power blip. - **The rest is the library's job.** At-least-once delivery, acking, reconnects, and — for production — `Nodrix.setCACert()` TLS pinning all sit below your handlers; the [downlink](https://nodrix.live/guides/esp32-receive-commands) and [HTTPS firmware](https://nodrix.live/guides/esp32-https-cloud) guides cover them in full. ## Build the dashboard Add the controls in the dashboard editor, each bound to a variable: | Widget | Bind to | Does | |---|---|---| | Toggle | `light_living` | switch the living-room light | | Toggle | `light_bedroom` | switch the bedroom light | | Toggle | `light_porch` | switch the porch light | | Toggle | `fan` | switch the fan | | Value | `light_porch` | show whether the porch light is on | Each toggle writes its variable, the library delivers it to the board, and the relay's echoed state settles the toggle — so the dashboard mirrors the house whether the change came from your thumb, a schedule, or a scene. Controls update live over a hibernating WebSocket, so nothing polls and an idle house costs almost nothing to keep connected. ## Automations that run without you The point of a smart home is the rules you don't touch. In nodrix these are **trigger → condition → action** flows evaluated at the edge — no code on the board. Build them in the automation editor. **Porch light at dusk.** A **sunset trigger** fires at your location's sunset and sets `light_porch` to `on`. A **schedule trigger** at, say, 6:30 in the morning sets it back to `off`. The porch now tracks the seasons on its own — no timer to reset when the days get shorter. **Fan on a schedule.** A schedule trigger can turn the `fan` on before you get home and off overnight. Add a condition later — only if a temperature variable is above a threshold — without rewiring anything; that's the [plant-watering pattern](https://nodrix.live/guides/esp32-automatic-plant-watering) of a sensor reading gating an action. **Goodnight, in one tap.** A **scene** is a saved set of variable states you apply together — `light_living`, `light_bedroom`, and `fan` off, `light_porch` on. Put a scene control on the dashboard to run it when you head to bed, and add a schedule trigger that applies the same scene at 11:30pm as a backstop. Because the board echoes state, every toggle settles to match the scene the moment it runs. Swap a channel — send to Slack or Telegram instead of switching a relay, or add an "only on weekdays" condition — and none of it touches the firmware. ## Control it from anywhere, privately The ESP32 opens the connection **outward** to nodrix, so there's nothing to expose — no port forwarding, no dynamic DNS, no VPN — and it works behind the strictest home router or cellular NAT. Port 443 is open everywhere, so the dashboard runs the house from the next room or another country. It's also yours: nodrix is single-tenant on **your own Cloudflare account**, so the device history, scenes, and schedules stay in your tenancy, not a shared vendor cloud. One project token authorizes the whole project — treat it as a secret and load it from config for anything permanent. ## When the internet drops A smart home has to fail gracefully: - **Wall switches never stop working.** The relays sit alongside the existing switches, so the house is always operable by hand. - **Cloud control pauses, then catches up.** While the link is down the board holds its last state; a command you send meanwhile is queued and delivered at-least-once the moment it reconnects, and automations resume on their own. - **Critical rules belong on the board.** If one routine must run during an outage — a safety cutoff, say — keep that single rule in the firmware and leave the convenience logic in the cloud. ## Going further - **Add a room by repeating.** Flash a second ESP32 with its own token and namespaced variables (`light_kitchen`, `light_garage`); one dashboard shows every board, no firmware change. - **Make it sense the house.** Feed a temperature, motion, or door sensor as telemetry and gate actions on it — fan on when it's warm, porch light on motion after dark. - **Dim instead of switch.** Drive a PWM channel or a dimmer module and bind a **slider** widget to a `brightness` variable for smooth control rather than on/off. - **Voice and presence.** Trigger scenes from an event the firmware emits, or from a phone-presence webhook, so "arriving home" sets the lights without a tap. ## Notes - **No hub, no broker.** The device speaks plain HTTPS and WebSocket; the house logic runs on your Cloudflare account, with nothing to keep alive at home. - **Configurable without reflashing.** Scenes, schedules, the sunset trigger, and every condition are edited in the dashboard — the firmware is flashed once. - **Single-tenant data.** Every state change stays in your own account, queryable through the read API. - **Scales by repeating, not rewriting.** The same sketch runs one room or the whole house; you add boards and variables, never new firmware. ### FAQ **Q: Can I control my ESP32 smart home from anywhere, not just on home Wi-Fi?** Yes. The board holds an outbound connection to nodrix in the cloud, so a toggle on the dashboard reaches it wherever it is — you're not on the same network, and you never open a port or set up a VPN. The device makes the connection outward over HTTPS, which every home router already allows, so it works behind NAT and captive portals. **Q: Do I need a hub or an MQTT broker for a DIY smart home?** No. There's no hub to keep powered and no broker to run or secure. The ESP32 talks straight to nodrix over a WebSocket, and the dashboard, automations, and scenes live in the cloud. The only always-on thing is Cloudflare, which you don't operate. **Q: How many lights or appliances can one ESP32 control?** As many as it has free GPIO pins — a common 4- or 8-channel relay board covers a room or two. Beyond that, flash a second ESP32 with its own token and namespace its variables (light_kitchen, light_garage). One dashboard shows every board at once; the firmware never changes. **Q: Is it safe to switch mains-voltage lights with an ESP32 relay?** Mains wiring is dangerous and, in many places, must be done by a licensed electrician. Use a properly rated, opto-isolated relay module, keep mains conductors enclosed and away from the ESP32's low-voltage side, and if you're unsure, switch low-voltage LED strips or smart plugs instead. The firmware is identical either way. **Q: Will my smart home keep working if the internet goes down?** Your physical wall switches always work — the relays don't remove them. Cloud control and automations pause while the connection is down and resume when it returns, and any command you send meanwhile is delivered once the board reconnects. If a routine must survive an outage, keep that one rule on the board itself. **Q: Does my smart-home data stay private?** It stays in your own Cloudflare account. nodrix is single-tenant and open-source — there's no shared vendor cloud holding your device history, and nothing leaves your tenancy. You hold the one token that authorizes the whole project. --- ## Guide: How to deploy nodrix to Cloudflare: a free-tier setup guide URL: https://nodrix.live/guides/deploy-nodrix-cloudflare Category: concept Deploy nodrix into your own Cloudflare account in a few minutes. Create a free account, run the one-click deploy, and understand the one step that trips people up — why Cloudflare asks for a card even though nodrix runs entirely on the free plan. nodrix deploys with **one click into your own Cloudflare account** — Workers, Durable Objects, D1, R2, and KV, provisioned and built for you, with no server to host. Most people are up and running in a few minutes. Two things trip up first-timers, though, and both are easy to clear up: you need a Cloudflare account, and partway through the deploy **Cloudflare asks for a credit card**. Neither one costs you anything for a normal setup. This guide walks the whole thing start to finish and explains exactly why that card prompt appears — because it's the step that gets people worried they're signing up for a bill, and they're not. ## What you're actually deploying The deploy drops a real, self-contained nodrix instance into **your** Cloudflare tenancy. It's single-tenant: the resources, the data, and the billing are all yours. The project never touches your account. Here's what gets created: | Cloudflare service | What nodrix uses it for | |---|---| | Workers | The app itself — API, dashboard, and static assets | | Durable Objects | Live variable state, dashboard sockets, the scheduler | | D1 | Metadata — users, projects, dashboards, tokens (never telemetry) | | R2 | Telemetry history (the cold store) | | KV | Read cache and JWKS | | Workflows | One-time provisioning on first boot | Every one of these runs on Cloudflare's **free plan**. Keep that in mind when the card prompt shows up — none of this requires a paid plan to work. ## Step 1 — Create a free Cloudflare account If you already have one, skip ahead. If not: 1. Go to [dash.cloudflare.com/sign-up](https://dash.cloudflare.com/sign-up). 2. Enter an email and a password. 3. Confirm the verification email Cloudflare sends. That's it — about two minutes, and **no card is requested at signup**. You now have a Cloudflare account on the free plan, which is all nodrix needs. ## Step 2 — Run the one-click deploy Hit **[Deploy to Cloudflare](https://nodrix.live/go/deploy)**. Cloudflare takes over from there and walks you through a short flow: 1. **Connect a Git account** (GitHub or GitLab). Cloudflare creates a small repository in your account to hold the deployment config — this is how you'll get updates later. 2. **Provision the resources.** Cloudflare creates the D1 database, the R2 bucket, the KV namespace, and the Durable Objects automatically. You don't fill anything in. 3. **Build and deploy.** The build pulls the latest nodrix release and ships it to your Worker. When it finishes, you get a `*.workers.dev` URL — that's your live nodrix instance. ## Step 3 — Why it asks for a card (and why it's not a bill) This is the screen that worries people. Somewhere in the flow, Cloudflare asks you to **add a payment method**, and it can look like you're being pushed onto a paid plan. You're not. The reason is **R2**, Cloudflare's object storage, where nodrix keeps your telemetry history. R2 is the one Cloudflare product that wants a payment method **on file before it will switch on** — even though its free tier covers far more than a maker deployment will ever use. Adding the card is **account verification**, not a charge: - It is **not** a plan upgrade. Your account stays on the free plan. - It is **not** a charge. Nothing is billed for staying inside the free allowance. - It **is** required — R2 won't activate without it, and nodrix needs R2 to store history. Add the card (or PayPal), continue, and the deploy completes. You won't see a charge appear. ## What runs on the free plan, and how much room you get Every service nodrix touches has a free tier, and they're generous. Approximate free-plan limits at the time of writing — see [Cloudflare's pricing](https://developers.cloudflare.com/workers/platform/pricing/) for the current numbers: | Service | Free allowance (roughly) | |---|---| | Workers | 100,000 requests/day | | D1 | 5 GB storage; millions of row reads/day | | KV | 1 GB storage; 100,000 reads/day | | Durable Objects | Included on the free plan; free-plan accounts aren't charged for SQLite storage | | Workflows | Included on the free plan | | R2 | 10 GB-month storage; 1M writes + 10M reads/month; **zero egress fees** | For context: a handful of devices each posting a reading every few seconds is a tiny fraction of those limits. The free tiers exist precisely for deployments this size. ## Will I ever be charged? Honestly: for a maker or small-team setup, **no**. The single thing that could eventually cost money is **R2 storage** — if you accumulated a very large volume of telemetry history, you could cross the 10 GB-month free mark. That's a lot of data points, and it's under your control through retention. A couple of reassurances on the things people specifically worry about: - **Durable Objects.** Cloudflare began billing for Durable Object SQLite storage in January 2026, but that applies only to **Workers Paid** accounts. Free-plan accounts are not charged for it. - **Egress.** R2 has **no egress fees**, so reading your own telemetry back out never costs bandwidth the way S3 would. If you want a hard guarantee, Cloudflare lets you set a [billing notification](https://developers.cloudflare.com/notifications/) so you're alerted long before anything approaches a charge. ## Step 4 — First boot With the deployment live, open your `*.workers.dev` URL: 1. The first visit shows a **Create owner account** page. The first signup becomes the `owner`; after that, registration is closed and the owner invites everyone else. 2. Create a **project** and mint a **project token** from the dashboard. 3. Point a device at it — variables auto-create the moment data arrives: ```bash curl -X POST https://.workers.dev/v1/telemetry \ -H "Authorization: Bearer $NODRIX_TOKEN" \ -H "Content-Type: application/json" \ -d '{"metrics":{"temperature":23.4,"humidity":61}}' ``` A reading lands, a widget appears, and you've got a working IoT backend on infrastructure you own. For the device side in full — including getting commands back to the hardware — see [Connect an ESP32 over HTTPS](https://nodrix.live/guides/esp32-https-cloud). ## Staying up to date Because the deploy created a repo in your Git account, updating is a one-click **Retry build** from the Cloudflare dashboard (Workers → your `nodrix` service), which pulls the latest release. nodrix also flags new versions for you under **Settings → Version & updates** and links you straight there. ## The short version - A Cloudflare account is **free** and takes two minutes; no card at signup. - The deploy provisions everything for you and runs on the **free plan**. - The card prompt is **R2 verification, not a bill** — add it and continue. - A normal deployment lives **well inside the free tiers**, and you own all of it. ### FAQ **Q: Do I need a paid Cloudflare account to run nodrix?** No. Everything nodrix provisions — the Worker, Durable Objects, D1, KV, Workflows, and R2 — runs on the Workers Free plan. There is no nodrix license fee either; it's open source. You only ever pay Cloudflare if your own usage grows past the free tiers, which for a maker or small-team deployment it won't. **Q: Why does Cloudflare ask for a credit card during deploy?** Because nodrix stores telemetry history in R2, Cloudflare's object storage, and R2 is the one product that wants a payment method on file before it switches on — even on its free tier. Adding the card is account verification, not a charge and not a plan upgrade. You won't be billed for staying inside the free allowance. **Q: Will I actually be charged anything?** For a typical maker or small-team workload, no. A handful of devices posting readings every few seconds sits comfortably inside Cloudflare's free tiers across every service nodrix uses. You'd only start to pay if you stored a very large volume of telemetry history in R2, and you control that with retention. **Q: I don't have a Cloudflare account — is it hard to set one up?** No. Sign up at dash.cloudflare.com with an email and password, confirm the verification email, and you're in — about two minutes, and no card is asked for at signup. The card prompt only appears later, during the deploy, when R2 is enabled. **Q: Can I deploy nodrix without putting a card on file at all?** Not for the full stack. R2 stores telemetry history and needs a payment method (card or PayPal) on file before it activates, so a working deployment requires one. It stays a verification step, not a bill — nodrix is built to run inside the free tiers. **Q: Is my data or billing shared with the nodrix project?** No. nodrix is single-tenant and deploys into your Cloudflare account. The resources, the data, and the billing relationship are all yours; the project never sees your telemetry or your Cloudflare account. --- ## Guide: The open-source Adafruit IO alternative you host on your own Cloudflare account URL: https://nodrix.live/guides/adafruit-io-alternative Category: comparison Looking for an Adafruit IO alternative without rate caps or data-retention limits? nodrix is open-source IoT you deploy to your own Cloudflare account — plain HTTPS/WebSocket, dashboards, automations, and a read API, with your telemetry in your own tenancy. Most people searching for an **Adafruit IO alternative** have run into one of three walls: the free tier's data-rate limit, the cap on how long history is kept, or simply wanting their feeds on infrastructure they own rather than a hosted cloud. nodrix is built for exactly that. It's open-source (MIT), and instead of signing up for a feed service you **deploy it to your own Cloudflare account** in one click — your feeds, dashboards, automations, and history all live in your tenancy, with no publish-rate ceiling and no per-account retention window. This is an honest comparison, including where Adafruit IO is the better pick. ## What Adafruit IO gets right Adafruit IO is a joy for learning. The tutorials are some of the best on the internet, the CircuitPython and Arduino libraries are tight, and there's a bundled MQTT broker so an always-on board can publish and subscribe with almost no code. If you're already in the Adafruit hardware ecosystem and want a dashboard this afternoon, it's hard to beat the on-ramp. For a classroom or a first IoT project, that polish is a real feature. What sends people looking is the model: it's a **hosted service** with a free tier that limits how often you can publish, how long your data sticks around, and how many feeds and dashboards you get. None of that is wrong for a freemium product — but it's the thing makers react to when a project outgrows the box. ## Adafruit IO vs nodrix, honestly | | Adafruit IO | nodrix | |---|---|---| | Model | Hosted SaaS (feeds + dashboards) | Open-source; you deploy it to your own Cloudflare | | Where data lives | Adafruit's cloud | Your Cloudflare account (single-tenant) | | Pricing | Free tier; IO+ yearly for higher limits | No license cost; you pay Cloudflare for usage | | Publish rate | Rate-limited on the free tier | No platform-imposed publish floor | | History retention | Capped by tier | Your own D1/R2 — you decide | | Device connection | MQTT + REST, Adafruit IO libraries | Plain HTTPS/WebSocket + optional open library | | Open source | Client libraries yes; platform hosted | MIT, full stack | | Automations | Actions / triggers | Visual trigger → condition → action, run at the edge | | Data access | REST API | Read API: latest state + time-series behind one token | | Mobile app | Web (responsive) | Responsive web (native app planned) | ## When Adafruit IO is the better choice - You want the **bundled MQTT broker** and an always-on board doing frequent pub/sub. - You're teaching or learning, and the **tutorial ecosystem** plus CircuitPython integration is the whole point. - You're comfortably inside the free tier or happy to pay for IO+, and you don't need to own the data layer. If that's you, Adafruit IO is a great answer and the ownership trade isn't worth it. ## When nodrix fits better - You've hit the **data-rate or retention limits** and want headroom that's only bounded by your own Cloudflare usage. - You want **open source and ownership** — your telemetry in your account, never on a third-party cloud. - You want a **device library's** convenience without the lock-in — an optional open Arduino library over a protocol any board can speak, not a vendor library or broker. - You want a **clean read API** to pull data into Grafana or your own app, plus **edge automations** you fully control. ## Moving a feed across The device side is tiny. Wherever your firmware publishes to an Adafruit IO feed, send the reading to nodrix instead — the metric key becomes a variable the first time it's seen: ```cpp #include #include DHT dht(4, DHT11); void setup() { dht.begin(); Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } void loop() { Nodrix.run(); Nodrix.send("temperature", dht.readTemperature()); // was feed("temperature")->save(t) } ``` Commands come back through a `NODRIX_WRITE` handler — the library polls or holds the control socket and acks for you. The full firmware is in [Connect an ESP32 over HTTPS](https://nodrix.live/guides/esp32-https-cloud). From there you rebuild your blocks as nodrix widgets and recreate any IO actions as trigger-condition-action flows. ## The bottom line If you value the Adafruit ecosystem and a hosted MQTT broker, Adafruit IO is a fine home. If you've outgrown the rate and retention caps — or you simply want open source, ownership, and a usage-based cost model — deploy nodrix to a spare Cloudflare account, point one device at it, and star the repo to follow along. ### FAQ **Q: Is there an open-source alternative to Adafruit IO?** Yes. nodrix is an open-source (MIT) IoT backend you deploy to your own Cloudflare account instead of signing up for a hosted feed service. Adafruit's client libraries are open source, but Adafruit IO itself is a hosted cloud — your feeds, dashboards, and history live on Adafruit's servers. With nodrix the whole stack lives in your tenancy. **Q: What are the Adafruit IO free tier limits?** The free tier caps how fast you can publish (a data-rate limit), how long history is retained, and how many feeds, dashboards, and actions you get; IO+ raises those for a yearly fee. People usually look for an alternative when they hit the data-rate or retention ceiling, or want their data off a third-party cloud. **Q: Do I need the Adafruit IO Arduino library to use nodrix?** You don't need Adafruit's library, and you're not locked to a nodrix one either. An optional open Arduino library removes the boilerplate — NODRIX_WRITE for commands, Nodrix.send for readings — and underneath it's plain HTTPS/WebSocket, so any board or language can POST to /v1/telemetry directly. Nothing is tied to a vendor protocol the way an Adafruit-specific library is. **Q: Does nodrix include an MQTT broker like Adafruit IO?** Adafruit IO bundles an MQTT broker, which is genuinely convenient for always-on, sub-second messaging. nodrix is HTTPS-first with a WebSocket path for instant control; for periodic telemetry and dashboards that's simpler, but if you specifically need a hosted MQTT broker, factor that in. **Q: How do I move a feed from Adafruit IO to nodrix?** Wherever your firmware publishes to an Adafruit IO feed (MQTT publish or the REST /data endpoint), send the same value to nodrix's /v1/telemetry instead — the metric key becomes a variable automatically. Then rebuild your blocks as nodrix widgets and recreate any IO actions as trigger-condition-action automations. --- ## Guide: An Arduino Cloud alternative for any board — open-source, on your own Cloudflare URL: https://nodrix.live/guides/arduino-cloud-alternative Category: comparison Arduino Cloud is polished but tied to the Arduino ecosystem and a hosted freemium plan. nodrix is an open-source alternative for any board that speaks HTTPS — deployed to your own Cloudflare account, no per-device limits, no lock-in. People look for an **Arduino Cloud alternative** for two main reasons: they don't want to be tied to the Arduino ecosystem (boards, IDE, plan limits), or they want an open-source stack they own rather than a hosted freemium service. nodrix answers both. It's open-source (MIT), it runs on **your own Cloudflare account**, and it's **board-agnostic** — anything that can make an HTTPS request talks to it, no SDK or particular board family required. Here's the honest comparison, including where Arduino Cloud is the better choice. ## What Arduino Cloud gets right For Arduino users, Arduino Cloud is genuinely slick. It's woven straight into the Arduino IDE: it generates the sync sketch for you, handles **over-the-air updates**, and its variable model keeps device and dashboard in lockstep with very little code. If you use official Arduino boards and live in that toolchain, it's a low-friction, well-supported path, and the integration is the whole point. The flip side is what sends people looking: it's a **hosted freemium** service (free plan caps Things, compile time, and dashboards), it's **centered on the Arduino ecosystem**, and your data lives on Arduino's cloud. ## Arduino Cloud vs nodrix, honestly | | Arduino Cloud | nodrix | |---|---|---| | Model | Hosted freemium SaaS | Open-source; deploy to your own Cloudflare | | Where data lives | Arduino's cloud | Your Cloudflare account (single-tenant) | | Boards | Arduino-centric (best with official boards) | Any board that speaks HTTPS | | Device code | IDE-generated sync sketch | Plain HTTPS/WebSocket (any language) + optional ESP library | | Pricing | Free plan with limits; paid tiers | No license cost; pay Cloudflare for usage | | Open source | No (platform) | MIT, full stack | | OTA updates | Yes | On the roadmap | | Dashboards | Hosted web + mobile | Responsive web, embeddable widgets | | Automations | Triggers / scheduler | Visual trigger → condition → action at the edge | | Maturity | Mature | Stable (v1.0), actively developed | ## When Arduino Cloud is the better choice - You're **in the Arduino ecosystem** — official boards, the Arduino IDE — and want the tight, generated integration. - **Over-the-air updates** are part of your workflow. - You want a polished hosted service and the free plan (or a paid tier) fits your project. ## When nodrix fits better - You use **mixed or non-Arduino hardware** — ESP32/ESP8266, Pico W, Raspberry Pi, custom — and want one backend for all of it. - You want **open source and ownership**: the stack and the data on your own Cloudflare account. - You don't want **per-device or plan limits**, and prefer costs that track real usage. - You want a **few-line device path** — an open Arduino library on ESP32/ESP8266, or plain HTTPS on anything else — instead of an IDE-locked sync sketch. ## Pointing a board at nodrix No IDE lock-in, and no generated sync sketch. On an ESP32 or ESP8266 the optional nodrix library makes it a few lines — `NODRIX_WRITE` for commands, `Nodrix.send()` for readings; the full firmware is in [Connect an ESP32 over HTTPS](https://nodrix.live/guides/esp32-https-cloud). The part Arduino Cloud can't match is everything else. Any board that speaks HTTPS talks to nodrix directly, no library or particular board family required: POST a reading to `/v1/telemetry` with the standard HTTP client, and pull commands back over `GET /v1/control` or the control WebSocket. An Arduino UNO R4 WiFi, a Nano 33 IoT, a Pico W, or a Python script on a Pi all speak the same open protocol — one backend, every board. ## The bottom line nodrix doesn't do OTA today — it's on the roadmap. If the Arduino toolchain integration and over-the-air updates are central to how you work right now, Arduino Cloud earns its place. If you want an open-source, board-agnostic backend you own outright, deploy nodrix to a Cloudflare account, point a board at it, and star the repo to follow along. ### FAQ **Q: Is there an alternative to Arduino Cloud that isn't tied to Arduino boards?** Yes. nodrix is board-agnostic: anything that can make an HTTPS request — ESP32, ESP8266, Raspberry Pi Pico W, a Raspberry Pi, an Arduino, or a Python script — can send telemetry and receive commands. There's no required SDK, IDE, or board family, though an optional open Arduino library gives ESP32/ESP8266 a few-line path if you want it. nodrix is open-source (MIT) and deploys to your own Cloudflare account. **Q: Is Arduino Cloud free?** Arduino Cloud is freemium: the free plan caps things like the number of Things, compile time, and dashboards, and paid plans lift those limits. nodrix has no license cost and no per-device limit; you pay Cloudflare for the usage of your own deployment. **Q: What does Arduino Cloud do better than nodrix?** Integration. If you live in the Arduino IDE and use official boards, Arduino Cloud is tightly woven in — automatic sketch generation, over-the-air updates, and a clean variable-sync model. nodrix doesn't replace that ecosystem; it trades it for being open-source, board-agnostic, and self-owned. **Q: Can I use nodrix with an Arduino board?** Yes — an Arduino with Wi-Fi (e.g. UNO R4 WiFi, Nano 33 IoT, MKR WiFi) talks to nodrix over plain HTTPS using the standard WiFi/HTTPClient libraries, exactly like an ESP32. You just point it at your endpoint instead of Arduino Cloud. **Q: Does nodrix do over-the-air firmware updates like Arduino Cloud?** Over-the-air firmware updates are on the roadmap. nodrix handles telemetry, dashboards, automations, and control writes today, with OTA planned. If OTA is essential to your workflow right now, factor that in; otherwise, stay connected as we add it. --- ## Guide: The open-source Blynk alternative that runs on your own Cloudflare account URL: https://nodrix.live/guides/blynk-alternative Category: comparison Looking for a Blynk alternative? nodrix is open-source IoT you deploy to your own Cloudflare account — no per-device pricing, no hosted cloud holding your data, plain HTTPS/WebSocket with dashboards, automations, and a read API. Most people searching for a **Blynk alternative** want one of three things: out from under per-device limits and template costs, their telemetry on infrastructure they actually control, or a fully open-source stack they can read and own. nodrix is built around exactly that. It's open-source (MIT), and instead of signing up for a hosted service you **deploy it to your own Cloudflare account** in one click. Your devices, dashboards, automations, and data all live in your tenancy — no broker, no per-device pricing, and no third-party cloud holding your readings. This is an honest comparison, including where Blynk is the better pick. ## What Blynk gets right Blynk earned its popularity. The mobile app is genuinely polished, the quick-start is fast, and the community and tutorial base are enormous — if you want to point a phone at a microcontroller this weekend, it just works. Its client libraries are open source and cover a wide range of boards. For a consumer-style project where a clean mobile app is the product, that's a real strength. What changed for a lot of makers is the model: the current platform (Blynk.IoT) is a **hosted commercial service**, the free tier is capped on devices and templates, and your data lives on Blynk's cloud. None of that is wrong — it's a SaaS — but it's the thing people are reacting to when they go looking for an alternative. ## Blynk vs nodrix, honestly | | Blynk | nodrix | |---|---|---| | Model | Hosted SaaS | Open-source; you deploy it to your own Cloudflare | | Where data lives | Blynk's cloud | Your Cloudflare account (single-tenant) | | Pricing | Freemium; paid plans scale by devices/usage | No license cost; you pay Cloudflare for usage | | Open source | Client libraries yes; platform hosted | MIT, full stack | | Device connection | Blynk libraries (vendor protocol) | Open Arduino library + plain HTTPS/WebSocket | | Dashboards | Native mobile app + web | Responsive web, drag-and-drop | | Widgets | App widget set | Framework-agnostic Web Components, embeddable anywhere | | Automations | Automations + events | Visual trigger → condition → action, run at the edge | | Data access | HTTPS API | Read API: latest state + time-series behind one token | | Native mobile app | Yes | Responsive web (native app planned) | | Maturity | Mature, large community | Stable (v1.0), actively developed | ## When Blynk is the better choice - You want a **first-class native mobile app** out of the box, with no front-end work. - You'd rather **not deploy or operate anything** — a hosted service is a feature, not a cost. - You're within the free tier or happy with the per-device pricing, and you don't need to own the data layer. If those are you, Blynk is a fine answer, and the ownership trade isn't worth it for your project. ## When nodrix fits better - You want **open source and ownership** — the stack on your own Cloudflare, your telemetry in your tenancy, your data never leaving your account. - You're allergic to **per-device pricing** and want costs that track actual Cloudflare usage. - You want the few-line convenience of a **device library** — `NODRIX_WRITE`, `Nodrix.send` — but over an **open protocol** you can drop anytime, not a vendor SDK or a broker. - You want a **clean read API** to plug telemetry into Grafana, a React app, or a Raspberry Pi screen, plus **edge automations** you fully control. ## Moving an ESP32 across The device side is small, and the shape is familiar: Blynk's `BLYNK_WRITE` handler becomes `NODRIX_WRITE`, and `Blynk.virtualWrite` becomes `Nodrix.send` — over an open protocol running on your own Cloudflare, not Blynk's cloud. ```cpp #include #include DHT dht(4, DHT11); NODRIX_WRITE("led") { // Blynk's BLYNK_WRITE(V1), minus the vendor cloud digitalWrite(LED_PIN, value.asBool()); } void setup() { dht.begin(); Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } void loop() { Nodrix.run(); Nodrix.send("temperature", dht.readTemperature()); // was Blynk.virtualWrite(V2, t) } ``` The library holds the control socket (or polls) and acks for you — the full firmware is in [Connect an ESP32 over HTTPS](https://nodrix.live/guides/esp32-https-cloud). From there you rebuild your widgets on a nodrix dashboard and recreate any Blynk automations as trigger-condition-action flows. ## The bottom line nodrix is the right call if you value open source, data ownership, and a usage-based cost model — with your dashboards on responsive web today and a native app on the roadmap. The useful move is to deploy it to a spare Cloudflare account, point one device at it, and star the repo to follow along. ### FAQ **Q: Is there an open-source alternative to Blynk?** Yes. nodrix is an open-source (MIT) IoT backend you deploy to your own Cloudflare account rather than signing up for a hosted service. Devices talk plain HTTPS or WebSocket, and the dashboards, automations, and data all live in your own tenancy. Blynk's client libraries are open source too, but its current platform (Blynk.IoT) is a hosted commercial service, not something you self-host. **Q: Why do people look for a Blynk alternative?** Usually one of three reasons: the free tier's device and template limits, wanting their telemetry on infrastructure they control instead of a third-party cloud, or wanting a fully open-source stack. If a polished mobile app and zero setup matter most, Blynk is hard to beat — the alternatives win on ownership and cost model. **Q: Can I self-host a Blynk alternative?** That's the idea behind nodrix — but rather than running a server, you one-click deploy it onto Cloudflare's serverless platform (Workers, Durable Objects, D1, R2). There's no broker, database, or VM to operate; you pay Cloudflare for what you use, and there's no per-device license. **Q: Does nodrix have a mobile app like Blynk?** A native mobile app is on the roadmap. Today, nodrix dashboards are responsive web and the widgets are framework-agnostic Web Components you can embed anywhere — including in your own app shell. If a first-class native app is essential right now, factor that in; otherwise, stay connected, as it's planned. **Q: How do I move an ESP32 from Blynk to nodrix?** Swap Blynk's virtual-pin writes for Nodrix.send(), and Blynk's BLYNK_WRITE handlers for NODRIX_WRITE — the same few-line shape, but over an open protocol on your own Cloudflare instead of a vendor cloud. The optional nodrix Arduino library handles Wi-Fi, control, and acks; underneath it's plain HTTPS/WebSocket you can use directly. Then rebuild your widgets on a nodrix dashboard and recreate any Blynk automations as trigger-condition-action flows. --- ## Guide: Datacake alternative with no per-device pricing URL: https://nodrix.live/guides/datacake-alternative Category: comparison nodrix is an open-source Datacake alternative you deploy to your own Cloudflare account — no per-device pricing, your telemetry in your own tenancy. People searching for a **Datacake alternative** are usually weighing one thing against its strengths: **per-device pricing** as a fleet grows. Datacake is a slick low-code platform, but the bill scales with devices and your data lives on its cloud. nodrix is the opposite trade — it's open-source (MIT) and you **deploy it to your own Cloudflare account** in one click, with no per-device license and every reading in your own tenancy. This is an honest comparison, including where Datacake is the better pick. ## What Datacake gets right Datacake nails low-code. The dashboard builder is fast, device templates make onboarding hardware easy, and the **LoRaWAN / The Things Network integration** is genuinely turnkey — for LPWAN sensor fleets that's a big head start. It also offers white-labeling and a rule engine, so for a reseller or a client deployment it's a capable, polished product you don't have to assemble. What sends people looking is the **cost model and ownership**: billing scales by device, and your telemetry sits on Datacake's infrastructure rather than your own. ## Datacake vs nodrix, honestly | | Datacake | nodrix | |---|---|---| | Model | Low-code hosted SaaS | Open-source; you deploy it to your own Cloudflare | | Where data lives | Datacake's cloud | Your Cloudflare account (single-tenant) | | Pricing | Per device | No license cost; you pay Cloudflare for usage | | Open source | No | MIT, full stack | | LoRaWAN / TTN | Turnkey integration | Via gateway/network-server webhook to /v1/telemetry | | Dashboards | Low-code builder | Drag-and-drop, embeddable Web Components | | Rules / automation | Rule engine | Visual trigger → condition → action at the edge | | Device connection | HTTP / MQTT / LoRaWAN | Plain HTTPS/WebSocket + optional open library | | Data access | API | Read API: latest state + time-series behind one token | ## When Datacake is the better choice - Your project is **LoRaWAN-first** and you want turnkey TTN integration and device templates. - You need **white-labeling** and a low-code builder for client or reseller deployments. - Per-device pricing is **fine for your fleet size** and you'd rather not own the stack. If that's you, Datacake is a strong, purpose-built answer. ## When nodrix fits better - You want to **drop per-device pricing** for costs that track actual Cloudflare usage. - You want **open source and ownership** — your telemetry in your account, not a third-party cloud. - Your devices speak **plain HTTPS/WebSocket** (or forward LoRaWAN payloads via webhook), and you want a device library without the lock-in — or none at all. - You want a **read API** for Grafana or your own app, plus **edge automations** you fully control. ## Moving a device across If your hardware already POSTs to Datacake's HTTP endpoint, repoint it at nodrix — each metric becomes a variable automatically: ```cpp // HTTPS POST https://nodrix.you.workers.dev/v1/telemetry // Authorization: Bearer tok_your_project_token // { "metrics": { "temperature": 23.4, "soil": 38 } } -> 204 ``` For LoRaWAN, set your network server's HTTP integration (the webhook that fires on uplink) to send the decoded payload to the same `/v1/telemetry` endpoint. Commands flow back via `GET /v1/control` or the control WebSocket — the full firmware is in [Connect an ESP32 over HTTPS](https://nodrix.live/guides/esp32-https-cloud). ## The bottom line If LoRaWAN and low-code white-labeling are the job, Datacake is a fine tool. But if per-device pricing is the friction — or you want open source, ownership, and a usage-based cost model — deploy nodrix to a Cloudflare account, point a device (or a network-server webhook) at it, and own the stack end to end. ### FAQ **Q: Is there an open-source alternative to Datacake?** Yes. nodrix is open-source (MIT) and you deploy it to your own Cloudflare account instead of paying per device on a hosted plan. Datacake is a low-code SaaS billed by device; nodrix has no per-device license and your data stays in your own tenancy. **Q: Does nodrix support LoRaWAN like Datacake?** Datacake's tight LoRaWAN / The Things Network integration is one of its real strengths. nodrix is HTTPS/WebSocket-first; a LoRaWAN device reaches it through a gateway or network-server webhook that forwards the decoded payload to /v1/telemetry. If turnkey LoRaWAN is central to your project, weigh that. **Q: How do I migrate from Datacake to nodrix?** Point your device's HTTP integration (or your LoRaWAN network-server webhook) at nodrix's /v1/telemetry instead of Datacake's HTTP endpoint — each metric becomes a variable automatically. Then rebuild dashboards and recreate Datacake rules as trigger-condition-action automations. --- ## Guide: Build an ESP32 automatic plant watering system URL: https://nodrix.live/guides/esp32-automatic-plant-watering Category: project · Board: ESP32 Calibrate a capacitive soil sensor, switch a pump safely through a relay, and let the cloud run the watering logic over a single WebSocket. A capacitive sensor and a small pump are all a plant needs to water itself. The hard part is deciding when to water without over-watering, doing it reliably when Wi-Fi flakes, and being able to retune later without unplugging anything. This build puts the sensor and pump on an ESP32 and keeps every decision in the cloud, so the firmware you flash once never changes. The board does two things: report a moisture number, and run the pump when told. The rules, dashboard, alerts, and safety checks live in nodrix on **your own Cloudflare account** — no broker to operate, no server to keep alive, no data leaving your tenancy. ## The idea: a dumb device and a smart cloud Bake the watering logic into the ESP32 and every tweak means reflashing — and the board can't tell you it's been watering hourly because the sensor came loose. Split it the other way: - **The device reports and reacts** — it sends `soil_moisture` and watches for a `pump` flag. That contract rarely changes. - **The cloud holds the logic** — thresholds, burst length, and the trigger-condition-action flow are edited in nodrix and apply on the next reading, no reflash. - **The cloud holds the memory** — every reading is stored and charted, so a misbehaving sensor is obvious at a glance. ## What you'll build - A live **soil-moisture gauge** and a rolling **24-hour chart** of the watering rhythm. - **Automatic watering**: the pump runs when the soil dries out and stops once it recovers. - A **pump toggle** for watering on demand, and a **value** readout of the current pump state. - A **Telegram alert** each time the plant is watered, and a scheduled **reservoir check**. ## What you'll need - An **ESP32** dev board (any common DevKit variant). - A **capacitive** soil-moisture sensor — not the resistive forks, which corrode within weeks. - A **5V pump**, a **relay module or logic-level MOSFET**, tubing, and a small reservoir. - A **separate 5V supply** sized for the pump's stall current — don't run the pump off the board. - The **Arduino IDE** with the ESP32 board package and the **Nodrix** library from the Library Manager (it pulls in ArduinoJson and WebSockets). - A **nodrix instance** with a project and a project token. ## Reading the soil A capacitive sensor outputs a voltage that tracks moisture — high in dry air, low when wet. The ESP32 reads it on a 12-bit ADC (0–4095), with two gotchas: - **Use an ADC1 pin.** ADC2 is shared with the Wi-Fi radio, so an analog read there returns nonsense once connected. **GPIO34** is on ADC1, input-only, and has no internal pull-up — ideal for a sensor output. - **Average the samples.** Raw readings jitter and are least accurate near the rails; averaging a handful smooths it. Calibration is two numbers: the raw value in open air (`DRY`) and fully submerged (`WET`). Everything between maps to a 0–100% scale. Those anchors shift with soil type and pot size, so calibrate in the setup you'll actually run — "30% moisture" only means anything relative to your `DRY` and `WET`. ## Wiring The sensor's analog output goes to **GPIO34**. The pump draws far more current than a GPIO can supply, so the ESP32 only switches a relay or MOSFET on **GPIO26**, and the pump runs from its own 5V supply — never off a board pin. | From | To | Wire | |------|----|------| | Soil sensor AOUT | ESP32 GPIO34 | Signal | | Soil sensor VCC | ESP32 3V3 | Power | | Soil sensor GND | ESP32 GND | Ground | | ESP32 GPIO26 | Relay IN | Signal | | Relay VCC | ESP32 5V (VIN) | Power | | Relay GND | ESP32 GND | Ground | | 5V supply + | Relay COM | Power (pump) | | Relay NO | Pump + | Power (switched) | | Pump | 5V supply | Ground | The sensor and relay share the ESP32's ground; the pump's separate supply feeds only the relay's load side. Tie the grounds together so the control signal has a common reference. ## Switching the pump safely A pump is an inductive, current-hungry load, and treating it like an LED kills boards. Three rules: - **Never drive it from a GPIO.** A pin sources a few milliamps; a pump pulls hundreds, more at stall. Switch it with a relay or logic-level MOSFET on the separate supply. - **Add a flyback diode** across the pump, cathode to **+**, to absorb the reverse spike when the motor switches off. - **Check relay polarity.** Many modules are active-low. The firmware below assumes active-high (`HIGH` = on); invert the two `digitalWrite` calls if yours differs. ## The control loop The loop is deliberately gentle, because soil and water are slow: 1. The ESP32 reports `soil_moisture` every few minutes. 2. Below **30%**, the automation sets `pump` to `on`. 3. The ESP32 runs a **short, capped burst**. 4. Above **60%**, the automation sets `pump` to `off`. The two thresholds give hysteresis: a single setpoint would make the pump chatter on sensor jitter, so turning on at 30% and only off at 60% builds a dead band that lets the soil wet through between decisions. And the burst is capped, not "run until wet" — water takes time to reach the probe, so it pours briefly, waits, and measures again. All of it lives in nodrix, so you retune from the dashboard without reflashing. ## The firmware One socket carries everything: moisture goes up, pump commands come down. The [nodrix Arduino library](https://github.com/decoded-cipher/nodrix-sdk) owns the socket, the acks, and the reconnects, so the sketch is only your logic — read the sensor, run one capped burst per command, and report the pump state back. ```cpp #include const char* WIFI_SSID = "your-ssid"; const char* WIFI_PASS = "your-password"; const char* HOST = "nodrix.you.workers.dev"; const char* TOKEN = "tok_your_project_token"; const int SENSOR_PIN = 34; const int PUMP_PIN = 26; const int DRY = 3200; // raw ADC reading in dry air — calibrate const int WET = 1300; // raw ADC reading submerged — calibrate const int BURST_MS = 5000; int readMoisture() { long sum = 0; for (int i = 0; i < 16; i++) { sum += analogRead(SENSOR_PIN); delay(10); } return constrain(map(sum / 16, DRY, WET, 0, 100), 0, 100); } NODRIX_WRITE("pump") { if (!value.asBool()) { digitalWrite(PUMP_PIN, LOW); return; } digitalWrite(PUMP_PIN, HIGH); delay(BURST_MS); digitalWrite(PUMP_PIN, LOW); Nodrix.send("pump", false); Nodrix.event("watered"); } void setup() { pinMode(PUMP_PIN, OUTPUT); digitalWrite(PUMP_PIN, LOW); Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } void loop() { Nodrix.run(); static unsigned long lastReading = 0; if (millis() - lastReading >= 5UL * 60 * 1000) { lastReading = millis(); Nodrix.send("soil_moisture", readMoisture()); } } ``` Worth understanding rather than copying: - **The burst is self-limiting.** It's a synchronous `digitalWrite` / `delay` / `digitalWrite`, so a pulse always ends even if Wi-Fi drops mid-pour — there's no path that latches the pump on. And because a command is delivered at-least-once, a "water now" sent while the board was offline still arrives on reconnect; a short burst repeating now and then just waters a little more. - **HTTP works too.** For a wake-report-sleep node, `Nodrix.beginHTTP()` with `Nodrix.poll()` reports the reading and collects any pending command per wake — same handler. - **Pin TLS before you ship.** `Nodrix.begin()` connects encrypted but unverified for the first run; add `Nodrix.setCACert()` for production, covered in [Connect an ESP32 over HTTPS](https://nodrix.live/guides/esp32-https-cloud). ## Build the dashboard Add four widgets, each bound to a variable: | Widget | Bind to | Shows | |---|---|---| | Gauge | `soil_moisture` | current moisture, 0–100% | | Chart | `soil_moisture` | the 24-hour watering rhythm | | Toggle | `pump` | manual water-now switch | | Value | `pump` | current pump state | The gauge and chart update live over a hibernating WebSocket. The toggle writes the same `pump` flag the automation uses, so manual and automatic watering share one path, and the device's echoed state keeps the toggle and value honest. The chart is the diagnostic that earns its place. A tuned system settles into a sawtooth — slow dry-down, sharp recovery, repeat. A flattening curve means water isn't reaching the probe (empty reservoir, slipped tubing); a sawtooth that's suddenly twice as fast usually means the sensor has shifted in the pot. ## Add the automation One automation runs the whole thing — two triggers routed by `if-variable` conditions. Build it in the automation editor. **Trigger 1 — a new `soil_moisture` reading:** - **Below 30** → set `pump` to `on`, then send a Telegram message like "Soil at {{value}}% — watering now." - **Above 60** → set `pump` to `off`. - **Between 30 and 60**, nothing happens — that gap is the hysteresis dead band. **Trigger 2 — a schedule (say, twice a day):** - **If `soil_moisture` is still below 30** → send a Telegram warning to check the reservoir. Soil that stays dry after watering usually means an empty tank or slipped tubing. Swap the integration for Slack, Discord, or SMS without touching the conditions. And because the firmware emits a `watered` event on every pour, you can branch off that event later without touching the board. ## Reliability and failure modes A self-watering system fails unattended, so design for two cases: - **The cloud is unreachable.** No command arrives, so the pump stays off — the safe default — and any in-flight burst finishes on its own. If watering must survive an outage, add a local fallback that runs a short burst on a low reading without the cloud. - **The reservoir runs dry.** Pumping air does nothing and can damage some pumps. The scheduled check catches moisture staying low despite watering, and the flat chart confirms it. ## Going further - **Run it on a battery.** Swap the always-open socket for a wake-report-sleep cycle over HTTP and a single cell lasts months — see [ESP32 battery life](https://nodrix.live/guides/esp32-deep-sleep-battery). - **Add plants by repeating.** Send `soil_moisture_2`, `soil_moisture_3`, and so on; each auto-creates its own variable. Add a gauge per plant and duplicate the automation — no firmware change. - **Dose by volume.** Replace the fixed burst with a measured one (flow rate × time, or a flow sensor) so each watering delivers a repeatable amount. - **React to the `watered` event.** Keep a watering log, post a daily summary, or escalate if waterings spike — all as event-triggered automations, none of it on the board. ## Notes - **No broker or server to run.** The device speaks plain HTTPS and WebSocket; nodrix runs on your Cloudflare account. - **Configurable without reflashing.** Thresholds, burst length, messages, and channels are all set in the dashboard — the firmware is flashed once. - **Single-tenant data.** Every reading stays in your own account, queryable through the read API. - **Scales by repeating, not rewriting.** The dumb-device contract is what lets one sketch run a windowsill or a greenhouse. ### FAQ **Q: Why does the watering logic live in the cloud instead of on the ESP32?** So you can change it without reflashing. Thresholds, burst length, alert channels, and the whole trigger-condition-action flow are edited in nodrix and take effect on the next reading. The board keeps one job — report a number, act on a flag — which is the part you don't want to be reprogramming every time you re-pot a plant or swap a sensor. **Q: How do I calibrate the dry and wet readings?** Read the raw analog value with the sensor in open air, then again fully submerged in water (or in soil you've just saturated), and put those two numbers in DRY and WET. They drift with soil type, pot size, and even the sensor batch, so calibrate in the exact setup you'll run. Everything downstream — the 0-100% scale, the 30% and 60% thresholds — is relative to those two anchors. **Q: Can the ESP32 switch the pump directly from a GPIO pin?** No. A GPIO sources a few milliamps; a pump wants hundreds. Drive the pump through a relay or a logic-level MOSFET powered from its own supply, share grounds, and put a flyback diode across the motor. Wiring a pump straight to a pin browns out the board at best and kills it at worst. **Q: Why two thresholds (30% and 60%) instead of one?** Hysteresis. With a single setpoint the pump would chatter on and off every time the reading jittered across the line. Turning on below 30% and only off again above 60% gives the soil room to actually wet through before the system reconsiders — the same reason a thermostat has a dead band. **Q: What happens if Wi-Fi or the cloud is unreachable?** The board reports nothing and receives no pump command, so it does nothing — the safe default. The capped burst is synchronous, so even if Wi-Fi drops mid-pour the pulse still finishes and stops on its own. If watering must survive an outage, add a local fallback that runs a short burst on a low reading without waiting for the cloud. **Q: Why GPIO34 for the sensor specifically?** It's an ADC1 pin, and ADC1 keeps working while Wi-Fi is on. The ESP32's ADC2 pins are borrowed by the radio, so an analog read there returns garbage once you're connected. GPIO34 is also input-only with no internal pull-up, which is exactly what an analog sensor output wants. **Q: Is the project token safe baked into the firmware?** Treat it as a secret. It scopes to this one project and all traffic is HTTPS, but don't commit it to a public repo — load it from NVS or a config file for anything real, and rotate it if it leaks. --- ## Guide: ESP32 battery life: deep sleep + cloud telemetry that lasts months URL: https://nodrix.live/guides/esp32-deep-sleep-battery Category: hardware · Board: ESP32 Make a battery-powered ESP32 sensor run for months while still reporting to the cloud. Deep sleep structure, RTC-memory Wi-Fi caching, a real power budget, and the dev-board traps that quietly kill battery life. A battery-powered ESP32 that reports to the cloud can run for **months** on a single 18650 cell — but only if it sleeps correctly. Battery life on the ESP32 is almost entirely a Wi-Fi problem: the radio dominates the power budget, so the whole game is to stay asleep, wake briefly, connect fast, send, and sleep again. Get the wake short and rare and the idle current low, and the math works out to a season or more between charges. Get any of those wrong and the same hardware dies in days. This guide is about the power side specifically: where the energy goes, the deep-sleep structure that makes it possible, connecting fast enough to matter, a real worked budget, and the dev-board traps that quietly wreck battery life. For the telemetry and control code itself, see [Connect an ESP32 over HTTPS](https://nodrix.live/guides/esp32-https-cloud). ## Where the power goes Three states matter, and they're orders of magnitude apart: | State | Current (typical) | When | |---|---|---| | Deep sleep | ~10 µA (bare module) | between readings — almost all the time | | Active + Wi-Fi | ~120-160 mA | the few seconds it's connecting and sending | | Peak TX burst | up to ~500 mA | brief spikes during transmit | The lesson reads straight off the table: a few seconds at 150 mA costs roughly the same as **hours** of deep sleep. Lifetime is set by how short and how infrequent the active bursts are — not by the sleep time. That's why "connect fast" is the highest-leverage optimization there is, and why a chatty board that stays awake "just in case" is the classic battery killer. ## The deep-sleep shape Deep sleep changes how you write the entire sketch. On wake the ESP32 powers the radio back up, **wipes RAM, and re-runs `setup()` from the top** — `loop()` effectively never runs. Anything that must survive a nap lives in **RTC memory**, declared with `RTC_DATA_ATTR`. So the structure is: everything happens in `setup()`, ending with a call to sleep; `loop()` stays empty. ```cpp void setup() { // wake → connect → read → send → poll → sleep } void loop() {} // intentionally empty for a deep-sleep design ``` ## Connect fast: cache the association The single biggest win is skipping the Wi-Fi channel scan. On the first connect, cache the BSSID and channel in RTC memory; on every wake after, hand them back to `WiFi.begin()` so it reconnects directly. A static IP skips DHCP for another saved second. Both shave seconds off the radio-on time, which — per the table above — is where the battery actually goes. ```cpp RTC_DATA_ATTR bool rtcValid = false; RTC_DATA_ATTR uint8_t rtcBssid[6]; RTC_DATA_ATTR int32_t rtcChannel; void fastConnect() { WiFi.mode(WIFI_STA); // Optional: a static IP skips DHCP entirely. // WiFi.config(ip, gateway, subnet, dns); if (rtcValid) WiFi.begin(WIFI_SSID, WIFI_PASS, rtcChannel, rtcBssid, true); // cached path else WiFi.begin(WIFI_SSID, WIFI_PASS); // first boot uint32_t start = millis(); while (WiFi.status() != WL_CONNECTED && millis() - start < 8000) delay(50); // bounded wait if (WiFi.status() == WL_CONNECTED) { memcpy(rtcBssid, WiFi.BSSID(), 6); rtcChannel = WiFi.channel(); rtcValid = true; } else { rtcValid = false; // bad cache → force a full scan next time } } ``` The bounded wait matters as much as the cache: never block forever on `WiFi.status()`. A bad night where the AP is unreachable should cost one capped attempt, not a flat battery. ## The full sketch Wake, connect, read the sensor, POST one reading, grab any queued command while the radio is up, and sleep on a timer. That's the entire life of a battery node. ```cpp #include #include #include #include #define SLEEP_MINUTES 15 const char* HOST = "nodrix.you.workers.dev"; // bare host, no https:// const char* TOKEN = "tok_your_project_token"; DHT dht(4, DHT11); void setup() { dht.begin(); fastConnect(); // RTC-cached association (above) if (WiFi.status() == WL_CONNECTED) { Nodrix.beginHTTP(HOST, TOKEN); // reuses the live connection Nodrix.send("temperature", dht.readTemperature()); Nodrix.flush(); // POST the reading Nodrix.poll(); // apply queued commands while we're up } esp_sleep_enable_timer_wakeup((uint64_t)SLEEP_MINUTES * 60ULL * 1000000ULL); esp_deep_sleep_start(); // execution ends here; wakes into setup() } void loop() {} ``` `Nodrix.poll()` is the downlink — it fetches queued writes, runs your `NODRIX_WRITE` handlers, and acks. It's worth doing every wake since the radio is already on; see [Receive commands on an ESP32](https://nodrix.live/guides/esp32-receive-commands). A sleepy device can't be pushed to instantly, but commands queued in the cloud land on the next wake. ## Do the math Plug your numbers in; here's a 15-minute cycle on a 2500 mAh cell: - **Per wake:** ~120 mA × 3 s = 0.1 mAh. - **Wakes/day:** 96 (every 15 min) → ~9.6 mAh/day from active time. - **Sleep/day:** a bare module at 10 µA adds ~0.24 mAh/day; a typical dev board at 0.2-0.3 mA adds ~5-7 mAh/day. - **Total:** ~10 mAh/day (bare) to ~16 mAh/day (dev board). - **Lifetime:** 2500 mAh ÷ 10-16 ≈ **160-250 days** — call it 5-8 months. The spread is almost entirely sleep current, which is why the board you choose matters more than shaving another reading. Stretch the interval to 30-60 minutes and you cross a year; drop to one minute and you're back to weeks. ## Squeeze more - **Pick the right board.** The biggest variable is idle current. A board with a USB-serial chip, a power LED, and a thirsty regulator can sit at hundreds of µA in "deep sleep." For real battery builds use a low-sleep board or power the bare module — the [XIAO ESP32 build](https://nodrix.live/guides/xiao-esp32-battery-sensor) works through exactly that trade on one popular board. - **Give the sensor its warm-up.** Many sensors need tens to hundreds of ms after power-up before a valid reading — budget it rather than reading `nan`. - **Buffer offline.** If a send fails, stamp the reading with your own `ts` (NTP-synced) and send it next wake, so a dropped network doesn't lose data. - **Mind the strapping pins.** Avoid GPIO 12 on the classic ESP32 (a strapping pin that can stop the board booting); safe wake/IO pins on the base chip include 4, 13, 14, 25, 26, and 27. - **Wake on more than a timer.** `esp_sleep_enable_ext0/ext1_wakeup` lets a reed switch, PIR, or button wake the board on an event instead of polling on a clock. ## Notes - **It's a Wi-Fi budget, not a sleep budget.** Optimize the radio-on time first; everything else is rounding error. - **HTTPS fits deep sleep perfectly.** There's no session to keep alive — wake, POST, poll, sleep. No broker, no persistent socket. - **Runs on your account.** Readings land in a nodrix instance on your own Cloudflare account, ready to chart, alert on, or read back through the API. ### FAQ **Q: How long can an ESP32 run on a battery while sending data?** With deep sleep between readings, a single 18650 cell can last months. A ~3-second wake every 15 minutes spends almost all its time drawing microamps in sleep, not the ~120-160 mA of an active Wi-Fi radio — so the radio's brief bursts, not the idle time, set the lifetime. **Q: What actually drains the battery?** The Wi-Fi radio, by a wide margin. Associating and transmitting pulls ~120-160 mA (with brief peaks higher); deep sleep is microamps. Battery life is almost entirely about how briefly and how rarely the radio is on, which is why connecting fast matters more than anything else. **Q: Why does my dev board drain fast even in deep sleep?** Usually the board, not the chip. Many dev boards keep a USB-serial chip, an always-on LDO regulator, or a power LED alive in sleep, turning the ESP32's ~10 µA into hundreds of microamps or more. For real battery builds pick a board designed for low sleep current, or power the bare module directly. **Q: Can a sleeping device still receive commands?** Yes, on its schedule. A deep-sleep device can't be pushed to instantly, but it polls the control endpoint right after it sends telemetry each wake and applies anything queued. Commands wait in the cloud and land on the next wake — seconds to minutes later, not never. **Q: Does deep sleep drop the Wi-Fi connection every time?** Yes — deep sleep powers down the radio and wipes RAM, so each wake reconnects from scratch. The fix is to cache the BSSID and channel (and optionally a static IP) in RTC memory so reconnect skips the scan and DHCP, cutting association from 3-4 seconds to under one. --- ## Guide: Connect an ESP32 to the cloud over HTTPS — a complete guide (no MQTT broker) URL: https://nodrix.live/guides/esp32-https-cloud Category: hardware · Board: ESP32 Push ESP32 sensor data to the cloud and receive commands back with the nodrix Arduino library — plain HTTPS underneath, no MQTT broker. Telemetry, control writes, and months of battery life with deep sleep. **Short version:** an ESP32 can push sensor readings to the cloud with nothing more than a Wi-Fi connection — no MQTT broker, no message queue to babysit. The [nodrix Arduino library](https://github.com/decoded-cipher/nodrix-sdk) wraps the whole thing: readings go up with `Nodrix.send()`, commands come down into a `NODRIX_WRITE` handler, and it's plain HTTPS underneath. This guide builds the full loop and a battery build that runs for months. It targets a nodrix instance on your own Cloudflare account, but the underlying technique works against any HTTPS API. Here is the entire uplink: ```cpp #include Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); Nodrix.send("temperature", 23.4); Nodrix.send("humidity", 61); // The variables show up on your dashboard instantly. ``` That is the part most tutorials stop at. The interesting half — getting a command *back* to the board to flip a relay or change a setpoint — is further down, and it's just as short. ## Should you even use HTTPS? (an honest take) MQTT is the default answer for IoT, and for good reason: on a persistent connection it is lighter per message and naturally bidirectional. But it also means running (or renting) a broker, keeping a socket alive, and handling reconnects. For a huge class of projects that is overkill. Use **HTTPS** when: - Readings are periodic — every few seconds to every few hours, not 50 times a second. - You want zero infrastructure to operate: no broker process, no queue. - The device lives behind awkward networks — corporate Wi-Fi, captive portals, cellular. Port 443 is allowed essentially everywhere; MQTT's 1883/8883 often is not. - You sleep the device between readings (battery sensors) — there is no persistent session to maintain anyway. Stick with **MQTT** when you need sub-second, high-frequency, or many-messages-per-second streams, true server-push with minimal latency, or you are fanning out to thousands of devices where the per-message savings dominate. For a temperature logger, a soil sensor, an energy monitor, or a parking-spot counter, HTTPS is not a compromise — it is the simpler correct choice. ## What you'll need - An **ESP32** dev board. This works on the classic ESP32, ESP32-S3, and ESP32-C3 (and on the ESP8266, which the library also supports). - A sensor. The examples use a BME280 (temperature, humidity, pressure) over I2C, but any reading works. - The **Arduino IDE** (or PlatformIO) with the [ESP32 board package](https://github.com/espressif/arduino-esp32) and these libraries from the Library Manager: **Nodrix**, **ArduinoJson**, and — for the BME280 — **Adafruit BME280** plus **Adafruit Unified Sensor**. - A cloud endpoint. We use a nodrix instance: deploy once to your Cloudflare account, create a project, and mint a **project token** — that token is the device's key. ## The mental model: two variables, two directions Forget topics and payload formats for a second. nodrix models a device as a bag of **variables**: - **Telemetry (up):** `Nodrix.send("temperature", 23.4)`. `temperature` becomes a variable, created automatically the first time it is seen. No schema to declare. - **Control (down):** a dashboard toggle, or one of your automations, queues a **control write** — "set `relay` to `on`". Your `NODRIX_WRITE("relay")` handler runs; the library acks it. One token, both directions. That is the whole protocol surface you need. ## Step 1 — Connect and secure the link `Nodrix.begin()` brings up Wi-Fi and opens the connection. TLS is the one decision to make up front: 1. **Default (insecure)** — traffic is *encrypted*, but you are not verifying *who* you are talking to. Fine for a first run on your own network; **never ship it.** 2. **Pin a root CA** — `Nodrix.setCACert(rootCA)` before `begin()`. Secure and maintainable; you update it only when the CA rotates. Pull the root CA like this and paste the last certificate into a `PROGMEM` string: ```bash openssl s_client -showcerts -connect nodrix.you.workers.dev:443 #include const char* HOST = "nodrix.you.workers.dev"; // bare host, no https:// const char* TOKEN = "tok_your_project_token"; Adafruit_BME280 bme; void setup() { Serial.begin(115200); bme.begin(0x76); // 0x76 or 0x77, per your board Nodrix.setCACert(ROOT_CA); // omit for the insecure default Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } ``` ## Step 2 — Send readings `Nodrix.send()` stages a metric; they coalesce into one request on the next `run()`. Send as many as you like — each key becomes its own variable: ```cpp void loop() { Nodrix.run(); static uint32_t last = 0; if (millis() - last > 10000) { last = millis(); Nodrix.send("temperature", bme.readTemperature()); Nodrix.send("humidity", bme.readHumidity()); } } ``` Open your dashboard and the `temperature` and `humidity` variables are already there. Drop a **value** or **gauge** widget on them and you are watching live data. Want a trend line? The **chart** widget plots a time window; the **map** widget takes lat/lng if you are tracking something that moves. ## Step 3 — Receive commands back (the half everyone skips) This is where "HTTP can't do downlink" turns out to be a myth. Register a handler for the variable; the library delivers each write, applies it through your code, and acks it so it isn't resent: ```cpp NODRIX_WRITE("relay") { digitalWrite(RELAY_PIN, value.asBool()); } ``` With `Nodrix.begin()` (the always-on WebSocket), writes arrive the instant a widget moves. For a sleepy device use `Nodrix.beginHTTP()` and call `Nodrix.poll()` once per wake to drain the queue. Either way the handler is the same — the library handles at-least-once delivery, acking, and reconnects underneath. ## Step 4 — Make it sip power: deep sleep done right A mains-powered board can just report on a timer. A battery board must **deep sleep** — and deep sleep changes how you write the whole sketch, because the ESP32 wipes RAM on wake and re-runs `setup()` from the top. `loop()` effectively never runs. Anything that must survive a nap goes in **RTC memory** with `RTC_DATA_ATTR`. The single biggest power drain is Wi-Fi association. Cache the BSSID and channel on the first connect and reuse them — this skips the channel scan and cuts reconnect from 3 to 4 seconds down to under a second. Bring Wi-Fi up yourself, then hand the live connection to the library: `beginHTTP` sees you're already connected and skips its own connect. ```cpp #include // deep-sleep API RTC_DATA_ATTR bool rtcValid = false; RTC_DATA_ATTR uint8_t rtcBssid[6]; RTC_DATA_ATTR int32_t rtcChannel; #define SLEEP_MINUTES 15 void fastConnect() { WiFi.mode(WIFI_STA); if (rtcValid) WiFi.begin(WIFI_SSID, WIFI_PASS, rtcChannel, rtcBssid, true); // cached path else WiFi.begin(WIFI_SSID, WIFI_PASS); // first boot uint32_t start = millis(); while (WiFi.status() != WL_CONNECTED && millis() - start < 8000) delay(50); if (WiFi.status() == WL_CONNECTED) { memcpy(rtcBssid, WiFi.BSSID(), 6); rtcChannel = WiFi.channel(); rtcValid = true; } else { rtcValid = false; // force a full scan next time } } void setup() { Serial.begin(115200); bme.begin(0x76); fastConnect(); if (WiFi.status() == WL_CONNECTED) { Nodrix.beginHTTP(HOST, TOKEN); // reuses the live connection Nodrix.send("temperature", bme.readTemperature()); Nodrix.send("humidity", bme.readHumidity()); Nodrix.flush(); // POST the batch Nodrix.poll(); // grab any queued commands while we're up } esp_sleep_enable_timer_wakeup((uint64_t)SLEEP_MINUTES * 60ULL * 1000000ULL); esp_deep_sleep_start(); // execution ends here; wakes back into setup() } void loop() {} // intentionally empty for a deep-sleep design ``` With a roughly 3-second wake every 15 minutes, a single 18650 cell can carry a sensor for months. A few extra wins: prefer a board without a power-hungry USB-serial chip on battery, give the sensor its warm-up time before reading, and avoid GPIO 12 on the classic ESP32 (it is a strapping pin that can stop the board booting). Safe wake and IO pins on the base chip include 4, 13, 14, 25, 26, 27. ## Production checklist - **Verify the certificate.** Set a pinned root CA with `Nodrix.setCACert()` before you deploy anything that matters — the default skips verification. - **Retry on a flaky network.** The library reconnects and re-flushes staged telemetry on its own; for deep-sleep devices, just report again on the next wake. - **Keep the token secret.** The project token is a credential. Do not commit it; load it from config or NVS, and rotate it if it leaks. - **Bound your own waits.** The one blocking spot you own is Wi-Fi association in a battery build — cap it (as above) so a bad night does not drain the cell. The protocol underneath is deliberately plain, which is why boards the library doesn't cover can still speak it directly — the [Arduino UNO R4 WiFi](https://nodrix.live/guides/arduino-uno-r4-wifi-cloud) build is the same contract written out by hand. And once a board is reporting, an [ESP32-S3 can run the model itself](https://nodrix.live/guides/esp32-s3-edge-ai) and send you the conclusion instead of the raw data. ## Troubleshooting - **Connection fails on first bring-up:** usually TLS. Drop `setCACert()` to fall back to the insecure default and confirm the rest works, then re-pin; check the host is the bare name with no `https://` and no trailing slash. - **Handshake fails after it worked:** the server's CA rotated and your pinned root is stale — re-pull it with the `openssl` command above. - **Resets or `Brownout detected`:** Wi-Fi transmit current spikes; power the board from a supply that can deliver about 500 mA, not a marginal USB port. - **Reading is `nan`:** the sensor was not given warm-up time after power-up, or the I2C wiring is off. ### FAQ **Q: Can an ESP32 really talk to the cloud without MQTT?** Yes. Underneath it's an HTTPS POST to one endpoint to send a reading and a GET to fetch queued commands — the library just wraps that. No broker is required for periodic telemetry. **Q: How do I get data back to the ESP32 over HTTP?** Register a handler for the variable with NODRIX_WRITE. The library polls a control endpoint (or holds a WebSocket open while awake), applies each write, and acks it for you. **Q: Is HTTPS too heavy for a microcontroller?** The first TLS handshake costs 1 to 3 seconds. After that it is quick, and for periodic telemetry the cost is irrelevant. Pinning a root CA keeps it both secure and maintainable. **Q: Will HTTPS telemetry drain my battery?** Not with deep sleep. Wake, send, poll, sleep. Cache the Wi-Fi BSSID and channel in RTC memory, hand the live connection to the library, and a single 18650 cell can last months. **Q: Does this lock me into one provider?** No. The pattern is plain HTTPS plus JSON. Here it targets a nodrix instance running in your own Cloudflare account, but any HTTPS API works the same way. --- ## Guide: Receive commands on an ESP32 from the cloud (the downlink, in depth) URL: https://nodrix.live/guides/esp32-receive-commands Category: hardware · Board: ESP32 How to get data back to an ESP32: handle control writes with the nodrix Arduino library over an always-on WebSocket, or poll on each wake for battery devices. The downlink most IoT tutorials skip. Sending a reading **up** is the easy half. The half that trips people up is getting a command back **down**: HTTP is request/response, so how does the cloud tell a device behind home Wi-Fi to flip a relay or change a setpoint? It doesn't, directly. The device asks. It fetches any pending **control writes**, applies them, and acknowledges the ones it handled so they aren't sent again — a pull, not a push, with no broker, static IP, or inbound connection. On an ESP32 or ESP8266 the [nodrix Arduino library](https://github.com/decoded-cipher/nodrix-sdk) runs that whole loop for you: fetch, apply, ack, reconnect. You write a handler per variable and the library calls it whenever the cloud writes that variable. This guide shows that, then explains what it does underneath — at-least-once delivery, idempotency, and acking. ## Two modes, matched to how the device lives The library connects one of two ways. Both share the same handlers and the same token, so you can start with one and switch later without touching the cloud side. | | `begin()` — WebSocket | `beginHTTP()` — poll | |---|---|---| | Latency | instant | your poll interval (seconds) | | Connection | one socket held open | none held; one request per check | | Best for | always-on controllers | sleepy / periodic devices | | Battery | poor unless mains-powered | excellent (sleep between polls) | | Cost when idle | ~zero (Cloudflare hibernates it) | one request per interval | ## Handle a control write A "command" is a **control write** — a pending instruction to set a variable: *`relay` to `on`*. Register a handler for the variable and the library runs it on every write, whether it came from a dashboard toggle, an automation, or the API: ```cpp #include NODRIX_WRITE("relay") { digitalWrite(RELAY_PIN, value.asBool()); } void setup() { pinMode(RELAY_PIN, OUTPUT); Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); // always-on WebSocket } void loop() { Nodrix.run(); // control in, telemetry out, acks, reconnect } ``` `value` coerces the wire value for you — `asBool()`, `asInt()`, `asFloat()`, `asString()` — so a toggle that sends `"on"` and a slider that sends `42` both just work. ## Battery / deep sleep: poll on each wake A sleepy device doesn't hold a socket. Switch to HTTP mode and drain the queue once per wake, right after you report — the board is already connected, so the extra request is nearly free: ```cpp #include // deep-sleep API void setup() { Nodrix.beginHTTP(WIFI_SSID, WIFI_PASS, HOST, TOKEN); Nodrix.send("soil", analogRead(34)); Nodrix.flush(); // POST telemetry Nodrix.poll(); // fetch + apply queued writes, then ack esp_sleep_enable_timer_wakeup(15ULL * 60 * 1000000); esp_deep_sleep_start(); } ``` Same handlers, same acks — a command simply applies on the next wake instead of instantly. ## What the library handles for you The downlink has a few sharp edges, and they're the reason to use the library rather than hand-roll it: - **At-least-once delivery.** A control write stays queued until it's acked and re-delivers on the next connect or poll, so nothing is lost across a nap or a Wi-Fi blip. The library acks every write it hands your code. - **Reconnect and catch-up.** On the socket it reconnects on its own and flushes anything queued while you were gone. - **One connection, both directions.** Telemetry, events, and acks share the socket with control — `Nodrix.send()` and `Nodrix.event()` go up the same pipe. ## Still your job - **Keep handlers idempotent.** At-least-once means you can see the same command twice. Setting a pin is naturally safe; for anything that isn't, act on the desired end-state rather than a toggle. - **Cap physical actions.** Prefer a self-limiting action — a timed pulse over a latch — so a missed "off" can't leave a pump or heater running. ## Notes - **No broker, no inbound connection.** The device only makes outbound HTTPS/WSS requests, so it works behind home routers, captive portals, and cellular NAT — port 443 is open everywhere. - **One token, one project.** The same project token authorizes telemetry, control, and the socket; treat it as a secret and load it from config for anything real. - **Install it.** Arduino Library Manager or PlatformIO; source and the [`LedControl` example](https://github.com/decoded-cipher/nodrix-sdk/tree/master/examples/LedControl) at [github.com/decoded-cipher/nodrix-sdk](https://github.com/decoded-cipher/nodrix-sdk). - **It runs on your account.** The control queue and dashboard live in a nodrix instance on your own Cloudflare account — single-tenant, nothing leaving your tenancy. ### FAQ **Q: Can you push data to an ESP32 over plain HTTP?** Not push, exactly — the device pulls. It polls a control endpoint on an interval and applies any queued writes, which gives near-real-time control without a broker. For instant updates while the board is awake, hold a WebSocket open and the cloud pushes down it. The library does either for you. **Q: How responsive is HTTP polling?** As responsive as your interval. Poll every 2-5 seconds and a dashboard toggle reaches the board in seconds; poll once per wake and a sleepy sensor picks up commands when it next reports. The trade is request volume and battery, not capability. **Q: What happens to a command sent while my device is offline or asleep?** It waits. Control writes are queued and delivered at-least-once: the cloud holds a write until the device acknowledges it, and re-delivers anything outstanding the moment the device reconnects or next polls. Nothing is lost across a nap or a Wi-Fi blip. **Q: Why do I have to acknowledge commands?** You don't, by hand — the library acks each write it delivers to your handler. Acking is how the cloud knows a write landed so it can stop re-sending it; without it the same command keeps coming back. Because delivery is at-least-once, every delivery is acked (even a duplicate), so keep your handler idempotent. **Q: Should I poll or use the WebSocket?** Use the always-on WebSocket (Nodrix.begin) for controllers that need zero-latency writes — on Cloudflare the socket hibernates, so an idle connection costs almost nothing. Use HTTP polling (Nodrix.beginHTTP, then poll each wake) for battery devices that wake, report, and sleep — there's no session to keep alive. --- ## Guide: ESP8266 to the cloud over HTTPS — a live dashboard with no broker URL: https://nodrix.live/guides/esp8266-iot-dashboard Category: hardware · Board: ESP8266 Push ESP8266 sensor data to a cloud dashboard with the nodrix Arduino library — BearSSL under the hood on the 8266's tight RAM, no MQTT broker, control writes back to the board, and the deep-sleep wiring that makes a battery sensor last. An **ESP8266** is more than capable of putting live sensor data on a cloud dashboard over HTTPS — no MQTT broker, no message queue to babysit. The [nodrix Arduino library](https://github.com/decoded-cipher/nodrix-sdk) supports the 8266 directly; it uses **BearSSL** (bundled in the [ESP8266 Arduino core](https://github.com/esp8266/Arduino)) under the hood, which matters because the chip's RAM is tight, so keep payloads small. This guide builds the whole loop on a real backend (nodrix, which deploys to your own Cloudflare account): a reading up, a command back down, and a deep-sleep build that lasts. Variables show up on your dashboard the first time they're seen — no schema to declare. ## The mental model Forget topics and payloads for a second. The board is just a bag of **variables**: - **Telemetry (up):** `Nodrix.send("temperature", 23.4)`; each key becomes a variable. - **Control (down):** a dashboard toggle or an automation queues a write — "set `relay` to `on`". Your `NODRIX_WRITE("relay")` handler runs; the library acks it. One token, both directions. That's the whole protocol surface. ## Step 1 — Connect `Nodrix.begin()` brings up Wi-Fi and opens the connection over BearSSL. Start on the default (encrypted but unverified) to get a green light, then pin a fingerprint for production: ```cpp #include #include const char* HOST = "nodrix.you.workers.dev"; // bare host, no https:// const char* TOKEN = "tok_your_project_token"; DHT dht(D2, DHT11); void setup() { Serial.begin(115200); dht.begin(); Nodrix.begin(WIFI_SSID, WIFI_PASS, HOST, TOKEN); } ``` ## Step 2 — Send readings `Nodrix.send()` stages a metric; they coalesce into one small request on the next `run()`. Keep the set of keys per call modest on this chip: ```cpp void loop() { Nodrix.run(); static uint32_t last = 0; if (millis() - last > 10000) { last = millis(); Nodrix.send("temperature", dht.readTemperature()); Nodrix.send("humidity", dht.readHumidity()); } } ``` Open your dashboard and `temperature` and `humidity` are already there. Drop a **value** or **gauge** widget on them and you're watching live data; the **chart** widget plots a time window. ## Step 3 — Receive commands back "HTTP can't do downlink" is a myth. Register a handler for the variable — the library delivers each write, runs your code, and acks it so it isn't resent: ```cpp NODRIX_WRITE("relay") { digitalWrite(RELAY_PIN, value.asBool()); } ``` With `Nodrix.begin()`, writes arrive the moment a widget moves. For a sleepy device, switch to `Nodrix.beginHTTP()` and call `Nodrix.poll()` once per wake. ## Step 4 — Deep sleep, the ESP8266 way A battery ESP8266 must **deep sleep** between readings, and there's one piece of hardware to get right: **wire GPIO16 to RST**. That jumper is how the chip wakes itself from a timed sleep — without it, `ESP.deepSleep()` puts the board to sleep and it never comes back. ```cpp #define SLEEP_MINUTES 15 void setup() { Serial.begin(115200); dht.begin(); Nodrix.beginHTTP(WIFI_SSID, WIFI_PASS, HOST, TOKEN); Nodrix.send("temperature", dht.readTemperature()); Nodrix.send("humidity", dht.readHumidity()); Nodrix.flush(); // POST the batch Nodrix.poll(); // grab queued commands while awake ESP.deepSleep((uint64_t)SLEEP_MINUTES * 60ULL * 1000000ULL); // wakes via GPIO16 -> RST } void loop() {} // intentionally empty for a deep-sleep design ``` Deep sleep wipes RAM and re-runs `setup()` from the top, so the whole sketch lives in `setup()` and `loop()` stays empty. Keep the awake window short — connect, send, poll, sleep. ## Production checklist - **Pin a fingerprint.** Call `Nodrix.setFingerprint(fp)` before `begin()` to verify the server on the 8266 — the default skips verification, and a full CA store is too heavy for this chip. - **Watch the heap.** Print `ESP.getFreeHeap()` during bring-up; TLS plus a large JSON buffer is the usual cause of a failed request. Keep the number of metrics per send modest. - **Keep the token secret.** The project token is a credential; load it from config, don't commit it. With a short wake every 15 minutes, an ESP8266 sensor reporting to a dashboard you own is a tidy, broker-free build — and the read API behind it means you can pull the same data into Grafana or your own app whenever you want. ### FAQ **Q: Can an ESP8266 do HTTPS to the cloud?** Yes — the nodrix library uses BearSSL (built into the ESP8266 Arduino core). The chip has far less RAM than an ESP32, so use the default (insecure) for a first run, then pin a fingerprint for production. A periodic HTTPS POST to one endpoint is well within its budget. **Q: Do I need MQTT for an ESP8266 dashboard?** No. Underneath it's an HTTPS POST to a single endpoint to send a reading and a GET to fetch queued commands — no broker to run. MQTT is worth it only when you need persistent, sub-second, high-frequency messaging. **Q: How is the ESP8266 different from the ESP32 here?** Same library and the same calls, three practical differences: it uses BearSSL under the hood; it has much less heap, so keep payloads small; and deep sleep needs a physical wire from GPIO16 to RST to wake the board. TLS pinning on the 8266 is a fingerprint (setFingerprint) rather than a CA. **Q: Will HTTPS drain an ESP8266 battery?** Not with deep sleep. Wake, send, poll, sleep. The TLS handshake costs a second or two, which is irrelevant against a 15-minute sleep. Wire GPIO16 to RST and a single cell can carry the sensor for a long time. **Q: Why is my ESP8266 connection returning -1?** Almost always TLS or memory. Try the insecure default to isolate certificate problems, and watch the heap — running out of RAM during the handshake also surfaces as a failed connection. Keep JSON payloads small. --- ## Guide: Home Assistant vs nodrix: local smart-home hub or your own cloud IoT backend? URL: https://nodrix.live/guides/home-assistant-vs-nodrix Category: comparison Home Assistant vs nodrix — they solve different problems. HA is a local home-automation hub for off-the-shelf devices; nodrix is an open-source cloud IoT backend you deploy to your own Cloudflare for custom hardware, remote dashboards, and a read API. Here's how to choose, and how they pair. **Home Assistant vs nodrix** isn't really a head-to-head — they're built for different jobs, and the honest answer for a lot of people is "both." Home Assistant is a **local home-automation hub**: you run it on a box at home and it ties together off-the-shelf smart devices with local control and an enormous integration library. nodrix is an **open-source cloud IoT backend**: you deploy it to your own Cloudflare account, and custom hardware POSTs telemetry over HTTPS to dashboards, automations, and a read API you own. This guide is about choosing the right one — and how they pair. ## What Home Assistant is for Home Assistant is the gold standard for the **local smart home**. If you've got Zigbee bulbs, Z-Wave locks, smart plugs, cameras, and Wi-Fi gear, HA speaks to thousands of them, runs automations locally (no cloud round-trip), keeps your data in your house, and has a polished mobile app and a massive community. For tying together *consumer devices* in one home, nothing else comes close. Its center of gravity, though, is **local and home-shaped**: it expects an always-on machine on your LAN, and reaching it from outside usually means a tunnel, a VPN, or the Nabu Casa cloud subscription. That's perfect for a house, and less aimed at custom hardware fleets reporting from the field. ## What nodrix is for nodrix is for **custom hardware and owned cloud telemetry**. An ESP32 soil sensor, a remote energy monitor, a parking-spot counter, a fleet of devices spread across sites — they POST plain JSON to a backend you deployed on Cloudflare, and you get realtime dashboards reachable from anywhere, edge automations, and a **read API** to pull data into Grafana or your own app. There's no home server to keep online or expose, because it runs at the edge. ## Home Assistant vs nodrix, honestly | | Home Assistant | nodrix | |---|---|---| | Category | Local home-automation hub | Cloud IoT backend for custom hardware | | Runs on | A box at home (Pi / NUC / VM), always on | Cloudflare (Workers, Durable Objects, D1, R2) | | Best for | Off-the-shelf smart-home devices | Custom devices, telemetry, fleets, remote dashboards | | Integrations | Thousands of device integrations | Plain HTTPS/WebSocket — any device; optional ESP library | | Remote access | Tunnel / VPN / Nabu Casa cloud | Public by default (it's already in the cloud) | | Data location | Your home machine | Your Cloudflare account (single-tenant) | | Automations | Local, very deep | Visual trigger → condition → action at the edge | | Read API | Via REST/templates | First-class: latest state + time-series, one token | | Open source | Yes | Yes (MIT) | ## When Home Assistant is the better choice - Your project is the **local smart home** — off-the-shelf Zigbee/Z-Wave/Wi-Fi devices. - You want **local control** with no cloud dependency and a huge integration catalog. - You're happy running and maintaining an **always-on home server**. ## When nodrix fits better - Your hardware is **custom** (ESP32/Pico/LoRaWAN) and you want it reporting to a cloud **you own**. - You need **dashboards reachable from anywhere** without exposing a home box. - You want a **clean read API** for telemetry and a stack with **no machine to keep patched at home**. ## Better together You don't have to pick. A common setup: custom field sensors report to **nodrix in the cloud**, and **Home Assistant pulls that data in** via its RESTful sensor integration against the nodrix read API: ```yaml # Home Assistant configuration.yaml — read a nodrix variable as a sensor rest: - resource: "https://nodrix.you.workers.dev/v1/projects//state" headers: Authorization: "Bearer " scan_interval: 60 sensor: - name: "Field soil moisture" value_template: "{{ value_json.state.soil.value }}" ``` HA owns the local smart home; nodrix owns the cloud telemetry for your custom hardware — each doing the half it's best at. ## The bottom line If you're automating a house full of off-the-shelf devices, run Home Assistant. If you're building custom hardware that needs cloud dashboards, automations, and a read API you own — without standing up and exposing a home server — deploy nodrix to your Cloudflare account. And if you're doing both, let them do what each is good at and bridge them through the read API. ### FAQ **Q: Is nodrix a replacement for Home Assistant?** Not really — they solve different problems. Home Assistant is a local hub for off-the-shelf smart-home devices (Zigbee, Z-Wave, Wi-Fi gear) with local control and a huge integration library. nodrix is a cloud backend for custom hardware: your ESP32/Pico sensors POST over HTTPS to a stack you deploy on your own Cloudflare account, and you get remote dashboards and a read API. Many people run both. **Q: Can I use Home Assistant and nodrix together?** Yes, that's a natural setup. Custom field sensors report to nodrix in the cloud (reachable from anywhere without exposing your home box), and you pull that data into Home Assistant via its REST/RESTful sensor integration using nodrix's read API. HA handles the local smart home; nodrix handles owned cloud telemetry. **Q: Does nodrix need an always-on machine at home like Home Assistant?** No. Home Assistant typically runs on a Raspberry Pi, NUC, or VM you keep online. nodrix is serverless — it deploys to Cloudflare (Workers, Durable Objects, D1, R2), so there's no home box to power, patch, or expose to the internet. --- ## Guide: Raspberry Pi Pico W to the cloud with MicroPython — a live dashboard URL: https://nodrix.live/guides/raspberry-pi-pico-w-iot-dashboard Category: hardware · Board: Raspberry Pi Pico W Send Raspberry Pi Pico W sensor data to a cloud dashboard with MicroPython over HTTPS — no broker. Full code for telemetry with urequests, commands back to the board, the onboard temperature sensor, and an honest note on Pico W TLS. A **Raspberry Pi Pico W** can put live sensor data on a cloud dashboard with a few lines of [MicroPython](https://micropython.org) — no MQTT broker, no SDK. You POST JSON to one HTTPS endpoint and poll a second one for commands. This guide builds the whole loop against a real backend (nodrix, which deploys to your own Cloudflare account), using the Pico W's own onboard temperature sensor so you can run it with no wiring at all. Variables appear on your dashboard the first time they're seen. ## The mental model The board is just a bag of **variables**: - **Telemetry (up):** POST `{"metrics": {"temperature": 23.4}}`; each key becomes a variable. - **Control (down):** a dashboard toggle or an automation queues a write — "set `led` to `on`". The board fetches pending writes, applies them, and acks. Two endpoints, one token. ## Step 1 — Connect Wi-Fi ```python import network, time, ujson, urequests from machine import Pin, ADC, deepsleep SSID = "your-ssid" PASS = "your-password" HOST = "https://nodrix.you.workers.dev" TOKEN = "tok_your_project_token" HEADERS = {"Authorization": "Bearer " + TOKEN, "Content-Type": "application/json"} def connect_wifi(): wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect(SSID, PASS) while not wlan.isconnected(): time.sleep(0.25) print("Wi-Fi up:", wlan.ifconfig()[0]) return wlan ``` ## Step 2 — Read a sensor and POST it No external sensor needed to start: the RP2040 has an onboard temperature sensor on ADC channel 4. ```python def read_temp_c(): raw = ADC(4).read_u16() * 3.3 / 65535 return 27 - (raw - 0.706) / 0.001721 # datasheet conversion def send_telemetry(metrics): body = ujson.dumps({"metrics": metrics}) r = urequests.post(HOST + "/v1/telemetry", headers=HEADERS, data=body) print("POST /v1/telemetry ->", r.status_code) # 204 = success r.close() # always close — frees the socket ``` Open your dashboard and the `temperature` variable is already there. Drop a **value** or **gauge** widget on it and you're watching live data; the **chart** widget plots a time window. > Always call `r.close()` after a urequests call. The Pico W has limited sockets, and leaking them > is the most common reason a long-running script stops sending after a while. ## Step 3 — Receive commands back The board asks for pending commands and applies them. The Pico W's onboard LED is a perfect test output — it's addressed as `Pin("LED")`. ```python def poll_control(): r = urequests.get(HOST + "/v1/control", headers=HEADERS) if r.status_code == 200: data = r.json() # { "control": [ { "id": "ctl_x", "variable": "led", "value": "on" } ] } for w in data.get("control", []): if w["variable"] == "led": Pin("LED", Pin.OUT).value(1 if w["value"] == "on" else 0) # POST w["id"] to /v1/control/ack so the platform stops resending it r.close() ``` Toggle the `led` variable from a dashboard control widget and the onboard LED follows. Poll every few seconds for near-real-time control. ## Step 4 — The main loop (and an honest note on sleep) On mains power, a simple loop is fine: ```python connect_wifi() while True: send_telemetry({"temperature": read_temp_c()}) poll_control() time.sleep(15) ``` For battery, you can deep sleep instead — but be realistic about the RP2040. `machine.deepsleep` resets the board and re-runs your script from the top on wake, like an ESP, yet its sleep-current floor is higher than an ESP32's, so the same duty cycle gives shorter battery life: ```python connect_wifi() send_telemetry({"temperature": read_temp_c()}) poll_control() deepsleep(15 * 60 * 1000) # milliseconds; board resets and re-runs on wake ``` If long battery life is the goal, an [ESP32](https://nodrix.live/guides/esp32-https-cloud) or [ESP8266](https://nodrix.live/guides/esp8266-iot-dashboard) with deep sleep will outlast a Pico W. The Pico W is at its best on mains power, short missions, or where you specifically want MicroPython and the RP2040's PIO. ## Production checklist - **Verify TLS for anything sensitive.** urequests encrypts but doesn't authenticate the server by default — pass an `SSLContext` loaded with the server certificate if confidentiality isn't enough. - **Close every response.** `r.close()` after each call; leaking sockets is the classic Pico W bug. - **Wrap network calls in try/except.** Wi-Fi blips happen — catch the exception, back off, and retry rather than crashing the script. - **Keep the token secret.** The project token is a credential; keep it out of shared code. That's a Pico W reporting to a dashboard you own, with commands flowing back — and the read API behind it means you can pull the same telemetry into Grafana or your own app whenever you like. The same MicroPython pattern runs unchanged [on an ESP32](https://nodrix.live/guides/esp32-micropython-cloud), and when a job outgrows a microcontroller entirely — real Python libraries, a camera, several processes — a [Raspberry Pi Zero 2 W](https://nodrix.live/guides/raspberry-pi-zero-2-w-iot) picks it up from there. ### FAQ **Q: Can a Raspberry Pi Pico W send data to the cloud?** Yes. In MicroPython, connect with the network module and POST JSON to a single HTTPS endpoint using urequests — each metric becomes a dashboard variable automatically. No MQTT broker and no SDK are required for periodic telemetry. **Q: MicroPython or Arduino for the Pico W?** Both work. MicroPython is the fastest way to get a Pico W onto a dashboard — urequests handles the HTTPS POST in a few lines. If you'd rather write the same C++ you'd use on an ESP32, the arduino-pico core gives you WiFi + HTTPClient and the ESP-style pattern applies unchanged. **Q: Does the Pico W verify the HTTPS certificate?** Be aware: MicroPython's urequests encrypts the connection but does not verify the server certificate by default — there's no CA store loaded, so it's confidential but unauthenticated, similar to setInsecure() on an ESP. For anything sensitive, pass an SSLContext with the server's certificate. The traffic is still TLS-encrypted either way. **Q: How do I get commands back to the Pico W?** Poll the control endpoint on an interval and apply what comes back, then ack it so it isn't resent. A dashboard toggle or an automation queues the write; the board reads it from /v1/control and flips the pin. **Q: Can the Pico W run on battery like an ESP32?** It can deep sleep with machine.deepsleep, but be honest about the RP2040: its sleep floor is higher than an ESP32's, so battery life is shorter for the same duty cycle. For long battery runs, an ESP32/ESP8266 with deep sleep is the better pick; the Pico W shines on mains power or short missions. --- ## Guide: A lightweight ThingsBoard alternative for makers — zero servers to run URL: https://nodrix.live/guides/thingsboard-alternative Category: comparison ThingsBoard is powerful but heavy to self-host. nodrix is an open-source alternative that one-click deploys to your own Cloudflare account — no Java, Postgres, broker, or VM to operate, with dashboards, edge automations, and a read API. People search for a **ThingsBoard alternative** rarely because ThingsBoard lacks features — it has plenty — and almost always because of **weight**. Self-hosting the Community Edition means standing up Java, a database (Postgres or Cassandra), and usually a message queue, then keeping all of it patched and alive. For an enterprise that's justified. For a maker or a small team, it's a lot of machinery to monitor a greenhouse. nodrix takes the opposite stance: it's open-source (MIT) and **one-click deploys to your own Cloudflare account** — Workers, Durable Objects, D1, and R2 — with **no server, broker, or database for you to run**. You still own everything (it's single-tenant, in your tenancy), but there's nothing to operate. This is an honest comparison, including where ThingsBoard remains the right tool. ## What ThingsBoard gets right ThingsBoard is a serious platform: a mature **rule engine** with rule chains, **multi-tenancy**, device provisioning, a broad protocol surface (MQTT, CoAP, HTTP, LwM2M), and a rich dashboard system. If you're building a product with many tenants, thousands of devices, or strict on-prem requirements, that depth is exactly what you want, and it's hard to match. The cost of that power is operational. Either you self-host and own the infrastructure, or you pay for ThingsBoard Cloud / Professional. Both are reasonable — they're just the thing makers are trying to avoid when they look for something lighter. ## ThingsBoard vs nodrix, honestly | | ThingsBoard | nodrix | |---|---|---| | To self-host | Java + Postgres/Cassandra + queue, on a VM/cluster | One-click deploy to Cloudflare; nothing to run | | Ops burden | You operate and scale it | Serverless; Cloudflare handles it | | Pricing | CE free (you host); Cloud/PE paid | MIT; pay Cloudflare for usage | | Protocols | MQTT, CoAP, HTTP, LwM2M, … | HTTPS + WebSocket (no broker) | | Rule engine | Deep rule chains | Visual trigger → condition → action at the edge | | Multi-tenancy | Yes | Single-tenant by design (one deploy = yours) | | Scale target | Enterprise / fleets | Makers and small teams | | Maturity | Mature, production-proven | Stable (v1.0), actively developed | ## When ThingsBoard is the better choice - You need **enterprise scale**, **multi-tenancy**, or a **mature rule engine** with complex chains. - You require **MQTT/CoAP/LwM2M** or other protocols beyond HTTP/WebSocket. - You have **on-prem** or specific data-residency requirements that a managed edge won't meet. For those, ThingsBoard is the grown-up answer and the operational weight is the price of admission. ## When nodrix fits better - You want **zero ops** — no Java, no database, no broker, no VM patching. - You want **open source you own** but deployed serverlessly to **your own Cloudflare account**. - Your devices speak **plain HTTPS/WebSocket** and your automations are maker-scale, not enterprise rule chains. - You'd rather pay **Cloudflare usage** than run (or rent) a cluster. ## The shape of the trade ThingsBoard gives you a powerful platform you must operate. nodrix gives you a narrower platform you don't have to operate at all — the same "your data, your infra" ownership, minus the servers. Pointing hardware at it is a plain HTTPS POST (see [Connect an ESP32 over HTTPS](https://nodrix.live/guides/esp32-https-cloud)); there's no broker to provision. If you need enterprise scale, multi-tenancy, or a deeper rule engine today, ThingsBoard's depth is the right call — and several of those are on the nodrix roadmap. If you're a maker or small team who wants ThingsBoard-style ownership without the ThingsBoard-style operations, deploy nodrix to a Cloudflare account, point a device at it, and star the repo to follow along. ### FAQ **Q: What's a lighter-weight alternative to ThingsBoard?** nodrix. ThingsBoard is a capable, enterprise-grade platform, but self-hosting it means running Java, a database (Postgres or Cassandra), and usually a message queue — real ops. nodrix instead deploys onto Cloudflare's serverless primitives (Workers, Durable Objects, D1, R2) in one click, with no server, broker, or database for you to operate. **Q: Is ThingsBoard free?** ThingsBoard Community Edition is open-source and free to self-host, but you carry the hosting and operations. ThingsBoard Cloud and the Professional Edition are paid. nodrix is open-source (MIT) with no license cost; you pay Cloudflare for the usage of your own deployment. **Q: Do I need MQTT for nodrix like I might with ThingsBoard?** No. ThingsBoard speaks MQTT, CoAP, HTTP, and more. nodrix is intentionally narrower: devices talk plain HTTPS or a WebSocket, with no broker to run. For periodic telemetry and command-and-control that's simpler; if you specifically need MQTT/CoAP/LwM2M at scale, ThingsBoard is the better fit. **Q: Can nodrix do a ThingsBoard-style rule engine?** Yes — nodrix has a visual automation builder (triggers, conditions, actions over HTTP/email/chat) evaluated at the edge, with deeper rule logic on the roadmap. It's single-tenant by design, where ThingsBoard targets enterprise multi-tenancy. **Q: When should I stay on ThingsBoard?** When you need enterprise scale, multi-tenancy, a mature rule engine, many device protocols, or on-prem requirements. ThingsBoard is built for that. nodrix targets makers and small teams who want zero ops and a single-tenant deployment they fully own. --- ## Guide: ThingSpeak alternative with no message cap or rate floor URL: https://nodrix.live/guides/thingspeak-alternative Category: comparison nodrix is an open-source ThingSpeak alternative you deploy to your own Cloudflare account — no annual message cap, no 15-second update floor. People reach for a **ThingSpeak alternative** for a handful of reasons: the fixed update-rate floor on the free tier, the annual message quota, the non-commercial restriction, or wanting realtime control and richer dashboards instead of mostly logging-and-plotting. nodrix covers those. It's open-source (MIT) and you **deploy it to your own Cloudflare account** in one click — your channels become variables in your own tenancy, with no per-account message cap and no minimum interval between readings. This is an honest comparison, including where ThingSpeak is the better tool. ## What ThingSpeak gets right ThingSpeak's superpower is analysis. Because it's a MathWorks product, the **MATLAB integration** is excellent — you can run real analytics and visualizations on your channel data, schedule them, and trigger reactions. For research, coursework, and anything where you want to *crunch* the numbers rather than just watch them, that's a serious advantage, and it's free for non-commercial use. The channel/field model is also dead simple to start logging into. What pushes people to look elsewhere is the shape of the free tier: a **minimum update interval**, a **yearly message limit**, and a **non-commercial** clause — plus dashboards that lean toward static plots rather than live, interactive control. ## ThingSpeak vs nodrix, honestly | | ThingSpeak | nodrix | |---|---|---| | Model | Hosted SaaS (channels + analytics) | Open-source; you deploy it to your own Cloudflare | | Where data lives | MathWorks cloud | Your Cloudflare account (single-tenant) | | Pricing | Free (non-commercial, capped); paid licenses | No license cost; you pay Cloudflare for usage | | Update rate | Minimum interval on free tier | No platform-imposed floor | | Message cap | Annual quota | Bounded only by your Cloudflare usage | | Focus | Data logging + MATLAB analytics | Realtime telemetry **and** control | | Downlink | TalkBack / React | First-class control writes (poll or WebSocket) | | Device connection | REST + MQTT, channel API key | Plain HTTPS/WebSocket + optional open library | | Open source | No (hosted) | MIT, full stack | | Data access | REST channel feed | Read API: latest state + time-series behind one token | ## When ThingSpeak is the better choice - You want **built-in MATLAB analytics** and scheduled analysis on your data. - You're in **academia or research** and the non-commercial free tier and citability fit. - Your project is **periodic logging plus offline analysis**, and live control isn't the point. If that's you, ThingSpeak is a strong, purpose-built answer. ## When nodrix fits better - You need **realtime** readings without a minimum-interval floor, and **control back to the device** as a first-class feature. - You've hit the **message cap** or the non-commercial restriction and want headroom bounded only by your own Cloudflare usage. - You want **open source and ownership** — your data in your account, not a third-party cloud. - You want a **read API** to pull telemetry into MATLAB, Python, or Grafana yourself, plus **edge automations** you control. ## Moving a channel across A ThingSpeak channel's fields map cleanly onto nodrix metrics. Wherever your firmware writes a channel update, POST the same values to nodrix — each key becomes a variable automatically: ```cpp // HTTPS POST https://nodrix.you.workers.dev/v1/telemetry // Authorization: Bearer tok_your_project_token // { "metrics": { "field1": 23.4, "field2": 61 } } -> 204 ``` Use real metric names instead of `field1`/`field2` and your dashboards get a lot more readable. Commands flow the other way via `GET /v1/control` or the control WebSocket — the full firmware is in [Connect an ESP32 over HTTPS](https://nodrix.live/guides/esp32-https-cloud). ## The bottom line If MATLAB analytics is the reason you're on ThingSpeak, stay — nothing here replaces that. But if you're fighting the update-rate floor, the message cap, or the non-commercial clause, or you want realtime control and a stack you own, deploy nodrix to a spare Cloudflare account, point one device at it, and pull the data wherever you want to analyze it. ### FAQ **Q: Is there a free, open-source alternative to ThingSpeak?** Yes. nodrix is open-source (MIT) and you deploy it to your own Cloudflare account, so there's no license cost and no annual message quota — you pay Cloudflare for usage, which for a handful of sensors is effectively free. ThingSpeak is a hosted MathWorks service; the free tier is non-commercial and rate-limited. **Q: Can nodrix send commands back to a device like ThingSpeak's TalkBack?** Yes, and it's first-class. A dashboard control or an automation queues a control write; the device fetches it from /v1/control and acks, or holds the control WebSocket open for instant writes. ThingSpeak can do downlink via TalkBack/React, but nodrix treats control as a core part of the protocol. **Q: Does nodrix do analytics like ThingSpeak's MATLAB integration?** Not built in — ThingSpeak's MATLAB analysis and visualization is genuinely its standout feature. nodrix instead exposes a clean read API (latest state + time-series behind one token) so you can pull data into MATLAB, Python, Grafana, or your own app and analyze it wherever you like. **Q: How do I move a ThingSpeak channel to nodrix?** Replace the channel field write (GET/POST to /update with your API key) with an HTTPS POST to nodrix's /v1/telemetry — each metric key becomes a variable automatically, so a channel's eight fields just become eight named metrics. Then rebuild your plots as nodrix chart widgets. --- ## Guide: A Ubidots alternative that drops per-dot pricing — open-source, on your Cloudflare URL: https://nodrix.live/guides/ubidots-alternative Category: comparison Looking for a Ubidots alternative without per-dot or per-device pricing? nodrix is open-source IoT you deploy to your own Cloudflare account — dashboards, events, automations, and a read API, with costs that track Cloudflare usage instead of data points. Most teams searching for a **Ubidots alternative** are reacting to one thing: **cost at scale**. Ubidots is a genuinely polished platform, but it's billed by data points or devices, and for a maker fleet or a growing project that meter adds up. nodrix takes a different shape. It's open-source (MIT) and you **deploy it to your own Cloudflare account** in one click — there's no per-dot or per-device license, just your Cloudflare usage, and every reading lives in your own tenancy. This is an honest comparison, including where Ubidots is the stronger choice. ## What Ubidots gets right Ubidots is built for businesses and it shows. The dashboards are clean and presentation-ready, the events/alerts engine is mature, it speaks a wide range of protocols (REST, MQTT, TCP/UDP), and the industrial tier brings **white-labeling**, prebuilt connectors, and managed support. If you're shipping a client-facing product and want a vendor to stand behind the platform, that's real value you'd otherwise have to build yourself. What sends people looking is the **pricing model**. Consumption-based billing is fine for a funded product with predictable volume; it's painful for hobby fleets, prototypes that suddenly scale, or anyone who'd rather own the stack than rent it by the data point. ## Ubidots vs nodrix, honestly | | Ubidots | nodrix | |---|---|---| | Model | Hosted commercial SaaS | Open-source; you deploy it to your own Cloudflare | | Where data lives | Ubidots' cloud | Your Cloudflare account (single-tenant) | | Pricing | By data points / devices | No license cost; you pay Cloudflare for usage | | Open source | No | MIT, full stack | | Dashboards | Polished, presentation-ready | Responsive web, drag-and-drop, embeddable | | Events / alerts | Mature events engine | Visual trigger → condition → action at the edge | | White-label / support | Yes (industrial tier) | Self-hosted; community + the repo | | Device connection | REST / MQTT / TCP-UDP | Plain HTTPS/WebSocket + optional open library | | Data access | REST API | Read API: latest state + time-series behind one token | ## When Ubidots is the better choice - You're shipping a **client-facing product** and need **white-labeling** and a vendor to support it. - You want **prebuilt industrial connectors** and a managed events engine out of the box. - Consumption-based pricing is **predictable for your volume** and you'd rather not own the stack. If that's you, Ubidots earns its price and the ownership trade isn't worth it. ## When nodrix fits better - You're **allergic to per-dot / per-device pricing** and want costs that track actual Cloudflare usage. - You want **open source and ownership** — your telemetry in your account, not a third-party cloud. - Your devices speak **plain HTTPS/WebSocket** and you want a device library without the lock-in — or none at all. - You want a **read API** to plug data into Grafana or your own app, plus **edge automations** you fully control. ## Moving a device across Ubidots variables map directly onto nodrix metrics. Swap the Ubidots POST for a nodrix one — each key becomes a variable automatically: ```cpp // HTTPS POST https://nodrix.you.workers.dev/v1/telemetry // Authorization: Bearer tok_your_project_token // { "metrics": { "temperature": 23.4, "battery": 87 } } -> 204 ``` Commands flow back via `GET /v1/control` or the control WebSocket — the full firmware is in [Connect an ESP32 over HTTPS](https://nodrix.live/guides/esp32-https-cloud). Rebuild your widgets on a nodrix dashboard and recreate Ubidots events as trigger-condition-action automations. ## The bottom line If you need white-labeling, industrial connectors, and managed support, Ubidots is a reasonable buy. But if the per-dot meter is the problem — or you want open source, ownership, and a usage-based cost model — deploy nodrix to a Cloudflare account, point a device at it, and watch the bill track usage instead of data points. ### FAQ **Q: Is there an open-source alternative to Ubidots?** Yes. nodrix is open-source (MIT) and you deploy it to your own Cloudflare account rather than paying for a hosted plan. Ubidots is a polished commercial platform billed by data points / devices; nodrix has no license cost and your bill is just your Cloudflare usage. **Q: Does nodrix have events and alerts like Ubidots?** Yes. nodrix automations are visual trigger → condition → action flows that run at the edge — e.g. when a variable crosses a threshold, call a webhook, send an email, or set another variable. It covers the common Ubidots events/alerts use cases without a separate events product. **Q: Is nodrix suitable for commercial or industrial projects like Ubidots?** It runs on Cloudflare's production platform (Workers, Durable Objects, D1, R2), so it's solid for commercial telemetry and dashboards. What Ubidots adds for industry is white-labeling, managed support, and prebuilt industrial connectors — if those are contractual requirements, weigh them; nodrix wins on ownership and cost model. **Q: How do I migrate from Ubidots to nodrix?** Replace the Ubidots variable POST (to /api/v1.6/devices/