@smartrsolutions/node-red-andon 1.3.0
Send values from Node-RED to the Andon app for iPhone: end-to-end encrypted tiles, fed by msg.topic.
Andon for Node-RED
Send values from Node-RED to Andon, the app for iPhone and iPad that shows your data as tiles and widgets. Everything is end-to-end encrypted: the content key stays in your Node-RED, and only the devices you paired can read what you send.
Get the app: Andon on the App Store.
[MQTT oee] ──┐
[S7 count] ──┼──► [andon out] ──► iPhone
[SQL order] ─┘
Two nodes:
- andon-view (configuration): one view. Its tiles come from a template you
design in the Andon configurator; its keys come
from the
.envthe configurator gives you. - andon out: takes values.
msg.topicis the tile ID,msg.payloadthe value. It collects, checks, encrypts and uploads them.
Getting started
Try the example
The example brings its own four tiles and a simulator that feeds them, so all it needs from you are the keys of a view.
- Install this package: Manage palette → Install, search for
andon. - Create a view in the configurator and
download the
.env. Leave the tiles alone, and do not download the template: the example has its own. - Pair your iPhone: scan the configurator's QR code with the Andon app.
- Import the example: Import → Examples → @smartrsolutions/node-red-andon → Andon basics.
- Open the view
Getting started(the pencil next to View inandon out) and click Import .env. Only that one: the simulator sends to the tiles the example came with, and another template would replace them. - Deploy. The node turns green, and the tiles appear on your iPhone.
Your own tiles
- Design your tiles in the configurator and
create the view there. Download the
.envand the template. - Pair your iPhone: scan the configurator's QR code with the Andon app.
- Add an
andon outnode to a flow. Next to View, add a new view and open it with the pencil, then click Import .env and Import template. - Connect your sources to the node:
msg.topicis the tile ID,msg.payloadthe value. The node's dialog lists what each tile takes, and Insert example flow puts a source in front of it that sends an example value to every tile. - Deploy.
Without the .env, the node is marked as not configured before you deploy.
Without the template, it shows no template once deployed: a view knows its tiles
from the template, and without one it takes a whole view document only.
Coming from the example, the step is the same: replace its simulator with your own sources, and import your template into its view once the tile IDs are yours.
A second example, All tile kinds, has one tile of every kind and a function node
that lists every form each of them takes: Import → Examples →
@smartrsolutions/node-red-andon → All tile kinds, then the .env as above. It is
what Insert example flow writes, for a view with every kind in it.
Messages
msg.topic is the ID of a tile, as the view lists it. What msg.payload does
depends on the tile:
| Tile | msg.payload |
Effect |
|---|---|---|
| label | number or text | replaces the value |
| gauge, progress, donut | number | replaces the value |
| line, bar over time | number | appended to the series, now or at msg.timestamp; msg.series picks the series |
| line, bar over time | array of [timestamp, value] pairs |
replaces the series; msg.series picks which |
| line, bar over time | {series: [{name, samples}]} |
replaces every series |
| line, bar over time | {series: [{name, start, stepSec, points}]} |
replaces every series: a point every stepSec from start |
| bar with categories, pie, donut | array of numbers, one per category | replaces the values; msg.series picks the series |
| bar with categories, pie, donut | {categories, series: [{name, values}]} |
replaces categories and values together |
| bar with categories, one series | {series: [{name, values, statuses}]} |
a verdict per bar: ok, warning, critical or null |
| table | array of rows, a row an array of cells | replaces the rows |
| table | array of {cells, status} |
replaces the rows, with a status per row |
| timeline | state ID | starts a new segment, now or at msg.timestamp; msg.lane picks the lane |
| timeline | {from, to} |
moves the time axis; what lies before from is trimmed |
| any tile | object, e.g. {"value": 79.3, "status": "warning"} |
sets the named fields; null removes one |
A number cannot be appended to a series in the step form: it has no timestamp of its own. Send pairs or a whole series instead; either replaces the step form.
The dialog of andon out lists these forms for the tiles of your view, and Insert
example flow writes all of them into a function node, one node.send each, and
below them, commented out, the same values as one message without msg.topic.
Numbers may arrive as text ("79.3"), as MQTT and most PLC nodes deliver them.
Timestamps are ISO 8601 in UTC, as Date.toISOString() writes them;
Date.toString() is refused. A whole history at a fixed step - hourly values since
midnight, say - is one series in the step form:
const midnight = new Date();
midnight.setHours(0, 0, 0, 0);
msg.topic = 't_visitors';
msg.payload = { series: [{ name: 'Today', start: midnight.toISOString(), stepSec: 3600, points: [12, 30, 41] }] };
A series has at least one point.
Without msg.topic, one message can carry several tiles - the natural shape for
a function node that has all its values at once, from one query or one API call:
return { payload: { t_oee: 79.3, t_shift: 580, t_reasons: [4, 7, 2] } };
Each value takes the same forms as with msg.topic. The message is taken or
refused as a whole, and msg.series, msg.lane and msg.timestamp apply to every
tile in it; for a value that needs its own, use msg.topic. Sent one by one or as
one message, values that arrive together go out in the same upload.
A whole view document as msg.payload (it has tiles) replaces the state,
template included - for a source that builds its tiles itself rather than filling
those of a template. generatedAt and staleAfterSec are set on upload.
A value that does not fit its tile is refused with an error a Catch node receives, and the error says what the tile takes. The view stays as it was, so one bad value never blocks the others.
The first real value of a series or timeline lane replaces the points the template came with: those are the configurator's start values, not measurements.
When it uploads
- A change goes out at most once per interval (30 s by default, 10 s at least). The first change after a quiet spell goes out at once.
- While values keep arriving, even unchanged ones, a heartbeat goes out after half the stale time, so the view stays fresh.
- When no value arrives, nothing is sent: the app then shows the view as stale, which is what it should show when your source has stopped.
- Devices are woken only when a tile's status changes (configurable). Every wake-up costs battery on every paired device.
Status
The status of a tile (ok, warning, critical, stale) is your judgement: the
app does not compute it from the value. Send it with the value, or tick
status from bands for a tile in the view to have it derived from its bands.
From the environment, for a container
A Node-RED in a container often has no editor to type a secret into, and no
credential store worth writing to. Set From the environment on the view to a
prefix — ANDON_, or ANDON_CLOUD_ for a second view in the same environment —
and everything the dialog leaves empty is read from there instead:
| Variable | What for |
|---|---|
<prefix>VIEW |
the view ID |
<prefix>WRITE_SECRET |
the write secret |
<prefix>CONTENT_KEY |
the content key, base64 |
<prefix>KEY_VERSION |
the key version, 1 if unset |
<prefix>URL |
the relay, if it is not the public one |
Those are the names the .env from the configurator already uses, so it can be
handed to the container unedited. A prefix has to be upper case and end in an
underscore, and it replaces the leading ANDON_ of each name.
What the view holds always wins; the environment fills in the rest. A variable nobody set makes the view say which one it was, and nothing is uploaded until it is there. The flow file then carries variable names and no secret at all — which is what makes a flow safe to commit, copy between machines and hand to a deployment.
The template stays in the flow: it is titles, icons, units and ranges, not a secret.
Security
- Write secret and content key are Node-RED credentials: stored encrypted in
flows_cred.jsonand never part of an exported flow. SetcredentialSecretin yoursettings.js. Or keep them out of Node-RED's files entirely and hand them in through the environment, see above. - The content key never leaves Node-RED. The write secret is sent only in the
Authorizationheader of the upload. - The output of
andon outis a summary (sizes, status code,notify), never the envelope or a secret. - The
.envfrom the configurator also holds the invite secret. This package drops it on import: a data source does not pair devices.
Requirements
Node-RED 3.0 or newer, Node.js 18 or newer.
The package is built on @smartrsolutions/andon-client,
which npm installs with it. To send from your own Node.js code, without Node-RED,
use that one: the same rules, values set by tile ID.
Contributing
The package is developed in a private repository next to the Andon relay and its specification; its GitHub repository is a public mirror that receives every release as one commit. Issues are welcome there, or by mail to [email protected]. Pull requests cannot be merged in the mirror, because the next release would overwrite them: a change we take arrives with the next release. Security problems: please write to [email protected] instead of opening a public issue.
License
Copyright 2026 SMARTR.solutions. MIT License; the text is in the file LICENSE of
this package. Versions up to 1.2.1 were published under the Apache License 2.0.