Gateway Contract#
The Leshan gateway and Django communicate over two internal HTTP APIs. The
golden files in contract/gateway/ record the exact requests and responses
and are the reference for both sides. contract/gateway/README.rst describes
their format and how to re-record them.
API |
Direction and base URI |
Configuration |
|---|---|---|
Ingest API |
Gateway to Django, |
|
Command API |
Django to gateway, |
|
Both APIs have no authentication. Run them on an internal network only.
Ingest API#
The gateway sends only two types of requests. Both are POST requests with
a JSON body. The gateway does not observe resources, so device values reach
Django only with an LwM2M Send or a read through the command API.
Registration Events#
The gateway posts each registration change to
/flownexus/ingest/registration with the body
{"ep": <endpoint>, "event": <event>, "time": <ISO 8601>}. Golden file:
contract/gateway/ingest/registration_lifecycle.json.
|
Cause and |
Django action |
|---|---|---|
|
LwM2M Register; registration time |
Sets the endpoint registered, reads |
|
LwM2M Update; time of the update |
Sends pending operations. |
|
LwM2M De-register or expired registration; gateway time of the event. Not sent when a new registration replaces the old one (device reboot). |
Sets the endpoint not registered. |
Django creates an unknown endpoint on the first event. It stores each event as
a resource of the internal object 10240 (0 registered, 1
unregistered, 2 updated) with the event time in epoch milliseconds as
value, so the database model and the frontend need no separate event table.
Object 10240 exists only in Django and is not part of the wire format.
An event older than the newest stored event of the endpoint, for example a
late retry, is stored but does not change the registration state.
After a registered event, the Celery task read_device_object reads
/3 through the command API and stores the content with the composite
resource serializer. Django, not the gateway, decides which objects to read
after a registration.
LwM2M Send#
The gateway posts each LwM2M Send to /flownexus/ingest/resource/timestamped
with the body {"ep": <endpoint>, "val": [{<timestamp>: {"nodes": {<path>:
<node>}}}]}. val has one entry per timestamp. The key is an ISO 8601
timestamp, or "null" if the device sent no timestamp; Django then uses the
time of reception. A node is the Leshan JSON form of an LwM2M node (kind,
id, type, value, values, instances, resources).
Golden file in |
Device input |
|---|---|
|
Send of one resource |
|
Send of resources from two objects |
|
Send of a whole object instance |
|
Send of cached values, each with its own timestamp |
Django creates an Event when a Send contains more than one path or
more than one timestamp. Django skips a resource that has no ResourceType
row.
Leshan rejects a Send that contains two values of one path with the same
timestamp, and the device receives 4.00. The Zephyr data cache stores
timestamps in seconds, so if a device writes a cached resource more often than
once per second, the device loses the whole Send.
Single and Composite Resources#
Django also accepts /flownexus/ingest/resource/single
({"ep", "obj_id", "val": <node>}) and
/flownexus/ingest/resource/composite ({"ep", "val": {"/<obj>":
<object node>}}). The gateway does not send these requests. The mock device
simulator in devtools/mock uses the single resource format, and
read_device_object uses the composite resource serializer directly. No
golden files exist for these formats.
Responses and Delivery#
Django answers 201 for a stored request and 400 for an invalid body.
The gateway handles the result as follows:
2xx: the request is complete.4xx: the gateway logs the error and drops the request.5xxor connection error: the gateway retries the request with exponential backoff (2 s, then doubled), 6 attempts in total. The last attempt starts about 62 s after the first one. After that, the gateway drops the request.
Each attempt has an HTTP timeout of 10 s. The gateway keeps pending retries in memory only, at most 10000 at a time, so a gateway restart loses them. The order of requests is not guaranteed: a retried request can arrive after later requests.
Command API#
Django sends commands to /api/clients/{endpoint}/{path} on the gateway.
{path} is an LwM2M path such as 3, 3/0 or 5/0/1.
Method |
LwM2M operation |
Path and body |
|---|---|---|
|
Read |
Any path. No body. |
|
Write (replace) |
Resource path. The body is one LwM2M node of kind |
|
Execute |
Resource path. An optional plain text body holds the execute arguments. |
Query parameters:
timeout: seconds the gateway waits for the device response, a positive integer. The default is 5 s.format: content format name for the downlink request, for exampleCBOR. Without it, Leshan selects the content format.
An invalid timeout, an unknown format, or a written resource whose
id differs from the resource ID in the path is answered with 400.
The response body is JSON. If the device answers, the body contains
status (Leshan response code name), code (CoAP code, for example
2.05), content (the read node, for a successful read only) and
error (the device error message, if the device sent one).
Status |
Meaning |
Body |
|---|---|---|
|
The device answered with success (CoAP |
|
|
Invalid path, body, query parameter or request. |
|
|
No registered client with this endpoint name. |
|
|
The device answered with an error (CoAP |
|
|
The device response is invalid. |
|
|
The client sleeps, or Leshan rejected or cancelled the request. |
|
|
The device did not answer within |
|
Golden file in |
Case |
|---|---|
|
Read of |
|
Accepted write, |
|
Execute without arguments, |
|
Write of a read-only resource, |
|
Write to an offline device, |
|
Command for an unregistered endpoint, |
|
Unknown format and mismatched resource ID, |
Use of the Command API in Django#
The Celery task send_operation sends an EndpointOperation to
{object}/{instance_id}/{resource}. instance_id defaults to 0.
Django sends a resource of data type NONE as execute without body, and
each other resource as write with format=CBOR. The task maps the response
to the operation status:
2xx:CONFIRMED.502withcode:FAILEDat once, without retry.400:FAILEDat once, without retry. The request is invalid.Each other status or a connection error:
QUEUEDfor a retry, andFAILEDafter 3 attempts. Django sends a queued operation again the next time it processes the pending operations of the endpoint, for example after a registration update.
A 504 does not prove that the device did not apply the operation, so a
retry can apply a write or an execute a second time.
Timeouts#
Timeout |
Value |
|---|---|
Device response (command API |
5 s (default and value that Django sends) |
HTTP request from Django to the command API |
Device timeout + 5 s |
HTTP request from the gateway to the ingest API |
10 s per attempt |
Ingest retries |
2, 4, 8, 16 and 32 s between the 6 attempts |