Nodes¶
A node is a piece of RTLS hardware — a tag transmitter, an anchor device, a gateway. Identified by a MAC address. Distinct from an Anchor (the placed beacon) and an Association (the binding between hardware and a placed entity).
Quick reference¶
| Method | What it does |
|---|---|
client.nodes.list(*, macs=None, uids=None) |
All nodes in scope, or filter by a list of MACs or uids (mutually exclusive). |
client.nodes.get(uid) |
Fetch one node by uid; raises NotFound if absent. |
client.nodes.create(mac_address, **fields) |
Register a new node. MAC is normalized. |
client.nodes.delete(uid_or_uids) |
Delete one (raises on failure) or many (continue-on-error BulkResult). |
client.nodes.release(mac_address, *, project_uid=None) |
Close open associations + release the node, optionally under a different project's scope. |
client.nodes.import_(mac_addresses, *, on_progress=None) |
Bulk import with per-item progress callback. |
client.nodes.validate(mac_addresses) |
Server-side pre-create check — returns which MACs would conflict. |
client.nodes.delete_raw / release_raw / assign_raw |
Single-call REST escape hatches. |
Reading and creating¶
A node is registered by its MAC address. Metadata (name, hw_info, etc.)
comes later via update_raw — the canonical commissioning flow is
MAC-only:
nodes = client.nodes.list()
node = client.nodes.create("aabbccddeeff")
# Filter the list by hardware MAC (normalized) or by server uid:
some = client.nodes.list(macs=["e4956ea5f707", "e4:95:6e:a6:33:75"])
some = client.nodes.list(uids=["n-1", "n-2"])
one = client.nodes.get("n-1")
list builds repeated query parameters — ?mac=…&mac=… or
?uid=…&uid=… — matching the server's filter wire format. The two
filters are mutually exclusive; passing both raises ValueError.
MAC addresses are normalized to bare 12-hex lowercase before the
request — the server's mac_regex is /^[a-fA-F0-9]{12}$/, no
separators allowed. Six input formats accepted on input
(lower/upper case, : or - separated, or bare 12-hex); the SDK
emits the canonical form. Malformed input raises ValueError before
any HTTP call.
Release with cross-scope override¶
release is the SDK's headliner for nodes: a node may have been
assigned under a different project than the client's default scope. The
compound switches scope for the duration of the call, closes any open
Associations, and releases the node — restoring the
original scope at the end. The override is per-thread, so concurrent
callers don't trample each other.
If a step mid-flight fails, the SDK raises PartialFailureError with
step="close_assoc" or step="release_node" so the caller can resume.
Bulk import¶
result = client.nodes.import_(
["aabbccddee01", "aabbccddee02", "aabbccddee03"],
on_progress=lambda done, total: print(f"{done}/{total}"),
)
import_ returns an ImportResult listing the MACs that succeeded
and the per-MAC error for those that failed. The progress callback
fires once per item.
Bulk delete¶
result = client.nodes.delete(["n-1", "n-2", "n-3"])
print(f"{len(result.successes)} deleted, {len(result.failures)} failed")
A single uid raises on failure; a list returns BulkResult and
continues on error.
Model — Node¶
uid and mac_address are optional because the embedded node
form inside a Trackable carries fewer fields than the
standalone-list form.
| Field | Type | Notes |
|---|---|---|
uid |
str \| None |
Server-assigned. |
mac_address |
str \| None |
Hardware MAC. |
name |
str \| None |
Optional display name. |
node_id |
int \| None |
Short numeric ID used by the RF protocol. |
node_type |
str \| None |
Node role/category. |
hw_info |
Any \| None |
Hardware-info sub-object (carries cbid, etc.). |
status |
Any \| None |
Server-reported status object. |
sw_version |
Any \| None |
Firmware/software version info. |
Parameter read / write¶
Hardware nodes carry runtime parameters (synchronisation slot, superframe descriptor, network config, schedule, …). Two methods expose the gateway endpoints used to push and read them.
# Push a parameter (fire-and-forget via the WIN gateway queue).
client.nodes.send_param(
"aabbccddeeff",
sync_slot={"addr": "1234", "slot": 0},
sf={"addr": "1234", "sfd": 1000, "bcs": 1, "tdoas": 0, "bsd": 0, "tsd": 0},
)
# Read current values back. Each row's field is either a
# `{"value": {...}, "elapsed": int}` dict or `None` (server-side
# "pending" — node hasn't reported yet).
rows = client.nodes.read_params(
["aabbccddeeff", "112233445566"],
fields=["sf", "sync_slot"],
force_update=False, # set True to bypass the gateway cache
)
for row in rows:
if row.sf is None:
print(row.mac_address, "pending")
else:
print(row.mac_address, row.sf["value"])
| Method | Wire endpoint |
|---|---|
send_param(mac, **plist) |
POST /api/v2/win_message_que/create.json/ |
read_params(macs, fields, *, force_update=False) |
GET /api/v2/hw_params.json?mac=…&fields=… |
send_param accepts any plist keys the firmware understands — sf,
sync_slot, nwk_cfg, srv_mode, tdoa_schedule, tdoa_schd, p.
Multiple keys in one call are valid; the SDK passes the plist
through unchanged.
For starting and stopping a TDOA sync network, prefer the
higher-level client.lsb.start() / client.lsb.stop()
which wrap these primitives in a poll-and-resend loop.
Related entities¶
- Anchors: a Anchor is the placed concept; a node is the hardware. Bound by an Association.
- Tags: Tags bind to nodes via association too.
- Associations: see Associations for the binding mechanics.
See also¶
- Error handling —
PartialFailureError.stepstrings forrelease. - Scope switching — internal per-call override that powers
release. - Compound workflows — release saga.