Skip to content

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.

client.nodes.release("aabbccddeeff", project_uid="other-project")

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.

Full field list.

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.

  • 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