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.
Before you start
Section titled “Before you start”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
curland Python 3- two terminal windows
Create an account
Section titled “Create an account”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.
Install the bridge
Section titled “Install the bridge”In your first terminal, run the installer:
curl -fsSL https://staging.nodelets.com/install.sh | shThe 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 and register the bridge
Section titled “Sign in and register the bridge”Sign in the Nodelets CLI:
nodelets loginThe 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:
nodelets bridge registerIt prints Registered this bridge with your Nodelets account and the bridge ID, which starts with br_. Check that the bridge is connected:
nodelets bridge statusThe Cloud line shows a green dot when the bridge is connected. If it does not, wait a few seconds and run the command again.
Run the practice thermostat
Section titled “Run the practice thermostat”Make a folder for this tutorial and save this program in it as device.py:
import jsonimport mathimport timefrom 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 /temperaturereturns{"temperature": 21.37}GET /targetreturns{"target": 20}PUT /targetwith a whole number as the body sets the targetGET /temperature/streamsends the temperature every five seconds as server-sent events
In your second terminal, start it from the tutorial folder:
python3 device.pyLeave 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:
curl http://127.0.0.1:8000/temperature{"temperature": 21.37}Describe the thermostat
Section titled “Describe the thermostat”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:
nodelets definition install thermostat.jsonInstalled definition practice-thermostatConnect the thermostat
Section titled “Connect the thermostat”Add the thermostat to the bridge as a Thing:
nodelets thing addThe 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.
Create an API key
Section titled “Create an API key”A program needs an API key to call Nodelets, and the key reaches only what its permissions list.
- On the dashboard, open Account.
- Under API keys, choose Create key.
- Enter
First device tutorialas the key name. - Set the permission line to Practice thermostat, All capabilities, Read value.
- 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:
export NODELETS_KEY='nlt_...'Read the temperature
Section titled “Read the temperature”List the Nodelets the key can reach:
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:
export NODELET='nl_5c0e8a2f-7d41-4b9e-a3c6-2f8d1e4b7a90'Read the temperature:
curl -fsS -H "Authorization: Bearer $NODELETS_KEY" \ https://api.staging.nodelets.com/nodelets/$NODELET/properties/temperature21.37The 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.
See a permission refuse a call
Section titled “See a permission refuse a call”The key can read but not write. Try to set the target:
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.
What you have now
Section titled “What you have now”- 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.