Skip to content

Read your first device

In this tutorial you go from nothing to reading a value from a device through the Nodelets API. You install a bridge on one machine, run a practice thermostat on that same machine, describe it with a definition, and read its temperature with curl.

The practice thermostat is a short Python program that answers HTTP requests with JSON, which is how the Nodelets HTTP driver talks to devices. Nothing in this tutorial needs real equipment.

You need:

  • a Linux machine with systemd, or a Mac, on amd64 or arm64
  • administrator rights on that machine, because the bridge installs as a system service
  • curl and Python 3
  • two terminal windows

Open https://dashboard.staging.nodelets.com and choose Create account. Enter an email address and a password.

Pre-release. Nodelets runs on staging only. Accounts and everything in them can be reset without notice, and there is no password reset, so keep your password somewhere safe.

In your first terminal, run the installer:

Terminal window
curl -fsSL https://staging.nodelets.com/install.sh | sh

The script downloads the nodelets executable for your system, checks it against a published SHA-256 checksum and installs it as a service. It asks for your administrator password once. It prints each step as it completes and ends with the next two commands to run.

Sign in the Nodelets CLI:

Terminal window
nodelets login

The command prints a link and a code. Open the link, sign in to the dashboard if it asks you to, check that the code on the Approve CLI access page matches the one in your terminal, and choose Approve. The terminal then says Signed in.

Register this machine’s bridge with your account:

Terminal window
nodelets bridge register

It prints Registered this bridge with your Nodelets account and the bridge ID, which starts with br_. Check that the bridge is connected:

Terminal window
nodelets bridge status

The Cloud line shows a green dot when the bridge is connected. If it does not, wait a few seconds and run the command again.

Make a folder for this tutorial and save this program in it as device.py:

import json
import math
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
target = 20
def temperature():
return round(21.5 + 0.5 * math.sin(time.time() / 60), 2)
class Device(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/temperature":
self.reply(200, {"temperature": temperature()})
elif self.path == "/target":
self.reply(200, {"target": target})
elif self.path == "/temperature/stream":
self.stream()
else:
self.reply(404, {"error": "not found"})
def do_PUT(self):
global target
if self.path != "/target":
self.reply(404, {"error": "not found"})
return
length = int(self.headers.get("Content-Length", "0"))
value = json.loads(self.rfile.read(length))
if type(value) is not int:
self.reply(400, {"error": "target must be an integer"})
return
target = value
self.send_response(204)
self.end_headers()
def stream(self):
self.send_response(200)
self.send_header("Content-Type", "text/event-stream")
self.end_headers()
try:
while True:
event = json.dumps({"temperature": temperature()})
self.wfile.write(f"data: {event}\n\n".encode())
self.wfile.flush()
time.sleep(5)
except (BrokenPipeError, ConnectionResetError):
pass
def reply(self, status, body):
data = json.dumps(body).encode()
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
ThreadingHTTPServer(("127.0.0.1", 8000), Device).serve_forever()

The thermostat has a temperature that drifts slowly around 21.5, and a target you can set. It answers four requests:

  • GET /temperature returns {"temperature": 21.37}
  • GET /target returns {"target": 20}
  • PUT /target with a whole number as the body sets the target
  • GET /temperature/stream sends the temperature every five seconds as server-sent events

In your second terminal, start it from the tutorial folder:

Terminal window
python3 device.py

Leave it running. It prints a line for every request it receives, so you can watch the bridge reach it. In your first terminal, check that it answers:

Terminal window
curl http://127.0.0.1:8000/temperature
{"temperature": 21.37}

The bridge knows nothing about a device until a definition says what it can do and how to reach each part. Save this as thermostat.json in the tutorial folder:

{
"formatVersion": 1,
"id": "practice-thermostat",
"title": "Practice thermostat",
"driver": "http",
"properties": {
"temperature": {
"type": "number",
"title": "Temperature",
"unit": "degree celsius",
"readOnly": true,
"observable": true
},
"target": {
"type": "integer",
"title": "Target",
"unit": "degree celsius",
"minimum": 5,
"maximum": 30
}
},
"bindings": {
"properties": {
"temperature": {
"readproperty": {
"method": "GET",
"path": "/temperature",
"responsePointer": "/temperature"
},
"observeproperty": {
"method": "GET",
"path": "/temperature/stream",
"stream": "sse",
"responsePointer": "/temperature"
}
},
"target": {
"readproperty": {
"method": "GET",
"path": "/target",
"responsePointer": "/target"
},
"writeproperty": {
"method": "PUT",
"path": "/target"
}
}
}
}
}

The properties section says what the thermostat has: a read-only temperature that can be watched, and a target between 5 and 30. The bindings section says how the bridge reaches each one: which path to call, and where the value sits in the JSON reply. responsePointer is a JSON Pointer, so /temperature picks the temperature field. Nothing is inferred: an operation with no binding cannot be called.

Install the definition on the bridge:

Terminal window
nodelets definition install thermostat.json
Installed definition practice-thermostat

Add the thermostat to the bridge as a Thing:

Terminal window
nodelets thing add

The command lists the installed definitions and asks four questions. Answer them like this:

Question Answer
Definition 1, or practice-thermostat
Name Practice thermostat
Base URL http://127.0.0.1:8000
Add HTTP headers? press Enter for no

It prints Added Practice thermostat and the Thing’s ID, which starts with nl_. The bridge publishes the Thing to your account, where it is a Nodelet with the same ID.

The base URL stays on the bridge. The cloud receives what the thermostat can do, never its address.

Your second terminal now shows the bridge opening GET /temperature/stream. Nodelets saves readings of every property that can be watched, so the bridge starts watching the temperature as soon as the Thing is added.

Open https://dashboard.staging.nodelets.com/bridges. The Bridges page shows your bridge as a box, with the Practice thermostat as a card inside it. The card lists Target and Temperature with what each can do. It shows no values: the page shows what the definition says, as it was when the page loaded. Reload the page if the card is not there yet.

A program needs an API key to call Nodelets, and the key reaches only what its permissions list.

  1. On the dashboard, open Account.
  2. Under API keys, choose Create key.
  3. Enter First device tutorial as the key name.
  4. Set the permission line to Practice thermostat, All capabilities, Read value.
  5. Choose Create key.

The dialog shows the key, which starts with nlt_. Copy it now: it is not shown again. In your first terminal, keep it in a variable:

Terminal window
export NODELETS_KEY='nlt_...'

List the Nodelets the key can reach:

Terminal window
curl -fsS -H "Authorization: Bearer $NODELETS_KEY" \
https://api.staging.nodelets.com/nodelets
{"items":[{"id":"nl_5c0e8a2f-7d41-4b9e-a3c6-2f8d1e4b7a90","name":"Practice thermostat","bridgeId":"br_9a4f2c71-3e8b-4d06-b5a2-c1e7f0d38b64","revision":1}]}

Keep the ID from your own output:

Terminal window
export NODELET='nl_5c0e8a2f-7d41-4b9e-a3c6-2f8d1e4b7a90'

Read the temperature:

Terminal window
curl -fsS -H "Authorization: Bearer $NODELETS_KEY" \
https://api.staging.nodelets.com/nodelets/$NODELET/properties/temperature
21.37

The request went from curl to the Nodelets cloud, over the bridge’s outbound connection to the bridge, and from the bridge to the thermostat. Your second terminal shows the thermostat receiving GET /temperature. The answer is the value at /temperature in the thermostat’s reply, checked against the definition’s number type.

The key can read but not write. Try to set the target:

Terminal window
curl -sS -X PUT -H "Authorization: Bearer $NODELETS_KEY" \
-d '22' \
https://api.staging.nodelets.com/nodelets/$NODELET/properties/target
{"error":{"code":"PERMISSION_DENIED","message":"PERMISSION_DENIED","correlationId":"req_...","outcome":"not-executed"}}

The cloud refused the call before it reached the bridge, and not-executed says the thermostat did nothing. Your second terminal shows no PUT request. The refusal is counted on the thermostat’s record, which you can open from Account, under Devices.

  • a bridge on your machine, connected to your account
  • a practice thermostat described by a definition and published as a Nodelet
  • an API key that can read the thermostat’s values and nothing else

To stop, press Ctrl+C in the second terminal. The Nodelet stays on your account. Reads then answer THING_UNAVAILABLE with outcome not-executed, because the bridge could not reach the device. To remove it, see Remove a device or a bridge.