HEART Embedded Systems logoHEART

HEART for IoT#

How a small connected device reports its health to HEART Hub: the light report, figures, sleeping, MQTT, and two short examples.

A sensor, a smart plug or a battery tracker can tell its owner how it is doing with a few lines of code. It sends plain JSON to HEART Hub. The Hub turns it into the same health page as any other device with a HEART. The rules are in the HEART Hub profile, Sections 2, 3, 5 and 7.

SectionYou will learn
1. What a device sendsThe three things a device does
2. The light reportIts fields, and what the Hub adds
3. AlertsHow an alert opens, repeats and ends
4. FiguresUp to 64 numbers on the device's page
5. SleepingHow a device that sleeps avoids looking dead
6. Changes from the ownerThe one thing that can come back, and how fast
7. Over MQTTTopics, and the bridge to the Hub
8. The examplesMicroPython, Arduino on an ESP32, and the bridge
9. What it can claimWhat a light-report device is, and is not
10. PrivacyFigures are published data

1. What a device sends#

A device does three things, in this order:

  1. It makes its identity once. At first start it makes a random device ID (16 hex characters), a claim code (20 characters) and a key (256 bits), from a cryptographic random source. It keeps them, and shows the claim code only to the person holding it: on a label, a screen or its console.
  2. It says hello. It sends the Hub the ID, and only the SHA-256 hashes of the claim code and the key. The owner then registers the device on the Hub with the ID and the claim code.
  3. It reports. Every half minute or so, or before it sleeps, it sends a light report with its key.

Nothing is stored until the owner registers the device. Until then, the Hub answers 202 and asks it to try again in five minutes.

2. The light report#

POST /v1/devices/3f9c0a71b2d4e865/report
Authorization: Bearer <device key>
Content-Type: application/json

{"status": "degraded",
 "alerts": [{"code": "power.battery-low", "severity": "warning"}],
 "figures": {"temperature": 21.5, "battery.volts": 3.71},
 "sleepSec": 900}
FieldNeededWhat it is
statusYeshealthy, degraded, failing or unknown: what the device itself thinks
alertsNoEvery problem it has now, at most 16, one per fault code. Each has a code such as power.battery-low and a severity: info, warning, error or critical.
figuresNoUp to 64 names and numbers
sleepSecNoHow long it will now be silent, in seconds, up to 7 days

The Hub checks it strictly. Any other field, a misspelt status, a fault code in the wrong form or a figure that is not a number, and it refuses the whole report with 400 and says why. Fix the report; never send it again unchanged.

What the Hub adds. A full HEART report is made of CloudEvents envelopes. A light report is not, so the Hub fills in what it needs: a sequence number that counts each report, a reporting interval of 30 seconds, an ID and a time for each new alert, and a title for each figure. It then stores exactly what it stores for a full report, and nothing more. A device that can build envelopes may still send the full report.

The answer. 200 {"claimed": true, "nextSec": 30} means stored: report again after nextSec seconds, or at once when something changes. Reports less than 10 seconds apart get 429. A wrong key gets 401: stop, and tell the owner. 404 means the Hub has forgotten the device: say hello again.

3. Alerts#

The device sends the list of its problems as they are now. The Hub works out the rest:

The device sendsThe Hub does
A fault code it did not send last timeOpens a new alert, detected now
The same code againKeeps the same alert, with the same ID and time, so a repeated alert is never counted twice
A list without that codeResolves the alert, now
The code with "status": "resolved"Resolves it, now

A device may give its own alert id (a UUID) and detectedAt time if it knows them. The owner sees each code explained in plain words on the device's page, from the standard list of fault codes.

4. Figures#

Figures are numbers the device chooses to share, such as a temperature, a battery voltage or a count. Up to 64 per report, each a name of lower-case letters, digits, . and -, and a number:

"figures": {"temperature": 21.5, "battery.volts": 3.71, "door.opens-today": 12}

The page shows each figure under its name. To give a title and a unit, send a list instead:

"figures": [{"name": "temperature", "title": "Room temperature", "value": 21.5, "unit": "°C"}]

The Hub keeps the latest figures, and two days of their history for the owner's AI model. The whole report stays within 16 KB.

5. Sleeping#

A battery device sleeps most of the time. Without warning, a sleeping device looks like one that has died. So it says how long it will sleep, in its last report before sleeping:

{"status": "healthy", "figures": {"temperature": 18.2}, "sleepSec": 3600}

Then:

  • Its page shows Asleep until that time, with its last health.
  • It counts as silent ("Not heard from") only after a grace: its usual 90 seconds, or a tenth of its sleep, whichever is longer. A device that sleeps for an hour is "Not heard from" 66 minutes after its last report.
  • Its owner gets no "gone quiet" email before then.
  • Its next report without sleepSec means it is awake.

sleepSec runs from 1 second to 604800 (7 days). A device that sleeps longer should wake up to report at least once a week.

6. Changes from the owner#

A device with a HEART is never commanded by the Hub. Reporting is one way. There is one narrow exception: a setting the device itself offers, within the range it sets, may be changed by its owner. The owner confirms each change with a code from their email. The Hub signs the request, and the device checks the signature, the time and the range before it acts, and may still refuse. The rules are in the HEART Hub profile, Section 7.2.

How fast. While the owner is making a change, the Hub opens a faster channel: it answers that device's reports with nextSec: 2, and accepts them 2 seconds apart. A change then reaches a device that follows nextSec about 2 seconds after the owner confirms it, and the result is on the page within about 4. The channel closes by itself: 2 minutes after the change, or 10 minutes at most while the device has not answered, and never more than 300 fast reports an hour.

A device that sleeps may stay awake for a while when it sees nextSec: 2, or keep sleeping: the change then waits for its next report.

The examples offer no settings, so they never take anything from the Hub. A device that offers settings must check every request exactly as Section 7.2 says, with the Hub's key pinned at its first hello.

7. Over MQTT#

HEART Hub is a website. It is not an MQTT broker. A device that already uses MQTT may publish to its own broker, and a small bridge relays to the Hub over HTTPS:

device ──MQTT──► broker ──► bridge ──HTTPS──► HEART Hub
TopicWho publishesWhat
heart/v1/hub/{deviceId}/helloThe deviceThe hello body
heart/v1/hub/{deviceId}/reportThe device{"key": "<device key>", "report": {...}}
heart/v1/hub/{deviceId}/replyThe bridge{"to": "report", "status": 200, "body": {...}}: the Hub's answer, unchanged
  • Every message uses QoS 1 and is never retained. MQTT 3.1.1 or 5.
  • The key travels in each report. So the broker must let only that device publish on its topics and read its replies, and only the bridge read the reports. Use TLS (port 8883) beyond your own network.
  • The bridge adds nothing and keeps nothing. If the Hub cannot be reached, the reply says 503.
  • A signed owner request comes back in the reply, and the device checks it as over HTTPS. The bridge cannot make one or change one.

Run the bridge with Python 3 and nothing else:

python3 iot-examples/heart_mqtt_bridge.py --broker mqtts://bridge:secret@broker.example:8883 --hub https://heartcortex.ai

This is not the mqtt binding of the API Standard, which carries a full HEART's own messages on a broker. The bridge serves only HEART Hub.

8. The examples#

The folder iot-examples/ has:

FileWhat it is
micropython_sensor.pyMicroPython on an ESP32: hello, then a light report every 5 minutes, deep sleep in between, with sleepSec
arduino_sensor/arduino_sensor.inoThe same for the Arduino core on an ESP32, with WiFiClientSecure and HTTPClient, checking the Hub's certificate
heart_mqtt_bridge.pyThe MQTT bridge, standard library only
test_iot_examples.pyTheir tests: the MicroPython example runs against a local copy of HEART Hub, and the bridge through a real broker

Each example makes its identity once, says hello, prints the link and claim code for its owner, and reports. Its own check of the sensor is its HEART: a reading outside the sensible range makes it degraded, with the alert sensors.out-of-range. When the reading is good again, the alert disappears from the list and the Hub resolves it.

9. What it can claim#

A device that sends light reports is watched: its owner sees its health from anywhere. On its own, that is not yet a HEART product. To claim HEART Basic · A1 ("Aware": it notices; a person does the rest) it must also meet Track 1:

  • notice when its main job stops, by itself, with no network (the examples do this);
  • tell a person nearby with no network, for example with a light or a buzzer;
  • answer with its Card and Pulse, itself or through a bridge such as HEART Line (Part 4, Section 12);
  • pass the tests of Track 1, and claim only what passed.

It never takes commands. The only thing that can come back is its owner's change to a setting it offered, signed by the Hub and checked by the device.

10. Privacy#

  • Figures are published data. Everyone who can see the device's page sees them, and the page can be shared. Send only numbers you would show to anyone you share it with. A room temperature or a door count can say when people are home.
  • No names, places or text. Figures are numbers, and alerts are standard fault codes. The Hub refuses anything else.
  • Pages are private until the owner shares them.
  • The key is a secret. Keep it on the device, send it only to the Hub over HTTPS (or through a broker you trust), and never print it.
  • Forgetting. The owner can forget a device at any time: its page, alerts and figures are deleted.