Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Each example is one sketch or script with a short README.
- Devices bring their own MQTT or HTTP client. infinimesh has no device SDK.
- MQTT topics: a device publishes to `devices/<id>/state/reported` and subscribes to `devices/<id>/state/desired`. `<id>` is the device uuid.
- Boards without TLS (for example ATmega Arduinos) should talk to a local gateway, such as a Raspberry Pi or an ESP32, which then connects to infinimesh over TLS. See the guide above.
- The HTTP path `POST /devices/states` may change to a smaller `/v1/ingest` endpoint later. We will update the examples when it does.
- HTTP devices send their own JSON with `POST /v1/ingest` and a device token, and read desired state with `GET /v1/ingest/desired`. The v1 API is stable, see [API versioning](https://github.com/infinimesh/core/blob/main/docs/api-versioning.md).
- The boards themselves have not been tested yet. What was tested is listed in each README.

## Check before you commit
Expand Down
43 changes: 25 additions & 18 deletions examples/curl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ A shell script that sends reported state to a device over the REST API and reads
## What it does

1. Gets a device token. Either you pass one, or the script makes one from an account login.
2. Sends `{"temp": 21.5, "source": "curl"}` as reported state with `POST /devices/states`.
3. Reads the state with `GET /devices/states` and prints this device's shadow.
2. Sends `{"temp": 21.5, "source": "curl"}` as reported state with `POST /v1/ingest`.
3. Reads the desired state with `GET /v1/ingest/desired` and prints it.

## Config

Expand All @@ -32,38 +32,45 @@ inf devices token <device-id> --allow-post
With the API (this is what the script does):

```bash
ACCOUNT=$(curl -s -X POST $INF_API/token \
ACCOUNT=$(curl -s -X POST $INF_API/v1/token \
-d '{"auth":{"type":"standard","data":["<user>","<password>"]}}' | jq -r .token)
curl -s -X POST $INF_API/devices/token -H "Authorization: Bearer $ACCOUNT" \
curl -s -X POST $INF_API/v1/devices/token -H "Authorization: Bearer $ACCOUNT" \
-d '{"devices":{"<device-id>":"MGMT"},"post":true}' | jq -r .token
```

Device tokens do not expire today. Keep them secret.
The token must name exactly one device and be allowed to post (`"post":true`). Without `expires_at` it expires after 24 hours. Keep it secret.

## Run

```bash
INF_API=https://api.example.com DEVICE_ID=<uuid> DEVICE_TOKEN=<token> ./infinimesh.sh
```

## The ingest API

`POST /v1/ingest` with `Authorization: Bearer <device token>` takes your own JSON object as the body. It becomes the reported state of the device in the token.

```bash
curl -X POST $INF_API/v1/ingest -H "Authorization: Bearer $DEVICE_TOKEN" \
-H "Content-Type: application/json" -d '{"temp":21.5}'
```

- `204 No Content`: stored.
- `400`: the body is not a JSON object, or it is larger than 16 KB.
- `401`: the bearer token is missing or invalid.
- `403`: the token names more than one device, or it may not post.
- `429`: more than 60 requests a minute for this device.

`GET /v1/ingest/desired` returns the desired JSON with an `ETag`. Send that `ETag` back as `If-None-Match` and add `?wait=30` (0 to 60 seconds) to wait for a change. It returns `304` when nothing changed.

## Expected output

```text
Posting reported state: temp=21.5
Reading state back
{
"device": "335ef65d-b82e-4b05-9c44-c14218713ebc",
"reported": {
"timestamp": "2026-10-12T11:39:33.291579569Z",
"data": {
"source": "curl",
"temp": 21.5
}
},
...
}
Reading desired state
{}
```

## Tested

Run live against infinimesh core (docker compose, REST gateway) on 12 October 2026, with both token paths.
The request and response codes follow core `pkg/ingest/handler.go` and [docs/api-versioning.md](https://github.com/infinimesh/core/blob/main/docs/api-versioning.md). On 12 October 2026 these requests were run against core's ingest handler from main: `204` for a post, `200` with the desired JSON, `400` for a non-object body, `401` without a token and `429` over the rate limit. The token calls were not rerun on the `/v1` paths.
17 changes: 9 additions & 8 deletions examples/curl/infinimesh.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,24 @@ if [ -z "$DEVICE_TOKEN" ]; then
: "${INF_USER:?set DEVICE_TOKEN, or INF_USER and INF_PASS}"
: "${INF_PASS:?set INF_PASS}"
echo "Getting an account token for $INF_USER" >&2
account_token=$(curl -fsS -X POST "$INF_API/token" \
account_token=$(curl -fsS -X POST "$INF_API/v1/token" \
-d "{\"auth\":{\"type\":\"standard\",\"data\":[\"$INF_USER\",\"$INF_PASS\"]}}" \
| json_get 'd["token"]')
echo "Getting a device token for $DEVICE_ID" >&2
DEVICE_TOKEN=$(curl -fsS -X POST "$INF_API/devices/token" \
DEVICE_TOKEN=$(curl -fsS -X POST "$INF_API/v1/devices/token" \
-H "Authorization: Bearer $account_token" \
-d "{\"devices\":{\"$DEVICE_ID\":\"MGMT\"},\"post\":true}" \
| json_get 'd["token"]')
fi

temp="${TEMP_VALUE:-21.5}"
echo "Posting reported state: temp=$temp" >&2
curl -fsS -X POST "$INF_API/devices/states" \
# The body is your own JSON object, at most 16 KB. Success is 204 No Content.
curl -fsS -X POST "$INF_API/v1/ingest" \
-H "Authorization: Bearer $DEVICE_TOKEN" \
-d "{\"device\":\"$DEVICE_ID\",\"reported\":{\"data\":{\"temp\":$temp,\"source\":\"curl\"}}}" \
> /dev/null
-H "Content-Type: application/json" \
-d "{\"temp\":$temp,\"source\":\"curl\"}"

echo "Reading state back" >&2
curl -fsS "$INF_API/devices/states" -H "Authorization: Bearer $DEVICE_TOKEN" \
| json_get 'json.dumps(next((s for s in d.get("shadows", []) if s.get("device") == "'"$DEVICE_ID"'"), {}), indent=2)'
echo "Reading desired state" >&2
curl -fsS "$INF_API/v1/ingest/desired" -H "Authorization: Bearer $DEVICE_TOKEN"
echo
22 changes: 9 additions & 13 deletions examples/esp32-http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ An Arduino sketch for the ESP32 that talks to infinimesh over HTTPS with a devic
## What it does

- Connects to WiFi and sets the clock with NTP.
- Every 30 seconds sends reported state with `POST /devices/states`.
- Then reads `GET /devices/states` and prints the desired part.
- Every 30 seconds sends reported state with `POST /v1/ingest`.
- Then reads `GET /v1/ingest/desired` and prints it.

Good for boards that wake up, send one value and go back to sleep.

Expand All @@ -28,21 +28,21 @@ Edit the top of `esp32-http.ino`:

## Request body

`POST /devices/states` takes a Shadow:
`POST /v1/ingest` takes your own JSON object, at most 16 KB:

```json
{"device": "<device-id>", "reported": {"data": {"temp": 21.5}}}
{"temp": 21.5, "uptime": 5}
```

Your own JSON goes inside `reported.data`. `GET /devices/states` returns `{"shadows": [...]}` for every device in the token.
It becomes the reported state of the device named in the token. Success is `204`. `400` means the body is not a JSON object or is too large, `401` a bad token, `403` a token that names more than one device or may not post, and `429` more than 60 requests a minute. `GET /v1/ingest/desired` returns the desired JSON, for example `{"led": true}`.

## Get a device token

```bash
inf devices token <device-id> --allow-post
```

or with the API, see [../curl/README.md](../curl/README.md). Device tokens do not expire today.
or with the API, see [../curl/README.md](../curl/README.md). The token must name only this device. Without `expires_at` it expires after 24 hours, so long-running devices need a token with a later `expires_at`.

## Build

Expand All @@ -54,16 +54,12 @@ arduino-cli compile --fqbn esp32:esp32:esp32 examples/esp32-http

```text
WiFi..... connected
POST /devices/states -> 200, sent {"device":"<id>","reported":{"data":{"temp":24.1,"uptime":5}}}
GET /devices/states -> 200, desired: "desired":{"timestamp":"...","data":{"led":true}
POST /v1/ingest -> 204, sent {"temp":24.1,"uptime":5}
GET /v1/ingest/desired -> 200, desired: {"led":true}
```

## Note

This endpoint may move to a smaller `/v1/ingest` endpoint later.

## Tested

- Compiles in CI for `esp32:esp32:esp32`.
- The same requests (body, headers, token) were tested live against core with the [curl](../curl) script on 12 October 2026.
- The same requests (body, headers, token) are the ones the [curl](../curl) script sends.
- It has not been run on a real ESP32 yet.
28 changes: 11 additions & 17 deletions examples/esp32-http/esp32-http.ino
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// infinimesh ESP32 HTTP example.
// Posts reported state with a device token every 30 seconds over HTTPS,
// then reads the device state back and prints the desired part.
// then reads the desired state and prints it.
//
// Uses only the Arduino esp32 core (WiFi, WiFiClientSecure, HTTPClient).

Expand Down Expand Up @@ -28,35 +28,29 @@ String auth;

void postReported() {
HTTPClient http;
http.begin(net, String(API_URL) + "/devices/states");
http.begin(net, String(API_URL) + "/v1/ingest");
http.addHeader("Authorization", auth);
http.addHeader("Content-Type", "application/json");
float temp = 20.0 + (esp_random() % 100) / 10.0;
// Body is a Shadow: device id plus reported.data with your own JSON.
String body = String("{\"device\":\"") + DEVICE_ID +
"\",\"reported\":{\"data\":{\"temp\":" + String(temp, 1) +
",\"uptime\":" + String(millis() / 1000) + "}}}";
// Body is your own JSON object (max 16 KB). It becomes the reported state.
String body =
String("{\"temp\":") + String(temp, 1) + ",\"uptime\":" + String(millis() / 1000) + "}";
int code = http.POST(body);
Serial.printf("POST /devices/states -> %d, sent %s\n", code, body.c_str());
if (code != 200) Serial.println(http.getString());
Serial.printf("POST /v1/ingest -> %d, sent %s\n", code, body.c_str());
if (code != 204) Serial.println(http.getString());
http.end();
}

void readDesired() {
HTTPClient http;
http.begin(net, String(API_URL) + "/devices/states");
http.begin(net, String(API_URL) + "/v1/ingest/desired");
http.addHeader("Authorization", auth);
int code = http.GET();
if (code == 200) {
// Response: {"shadows":[{"device":..., "reported":{...}, "desired":{"data":{...}}}]}
String res = http.getString();
int i = res.indexOf("\"desired\"");
Serial.printf("GET /devices/states -> 200, desired: %s\n",
i >= 0
? res.substring(i, res.indexOf('}', res.indexOf("\"data\"", i)) + 1).c_str()
: "none");
// Response: the desired JSON, for example {"led":true}
Serial.printf("GET /v1/ingest/desired -> 200, desired: %s\n", http.getString().c_str());
} else {
Serial.printf("GET /devices/states -> %d\n", code);
Serial.printf("GET /v1/ingest/desired -> %d\n", code);
}
http.end();
}
Expand Down
Loading