Explorer
Explorer methods discover sources and point lists for devices behind an edge node. Use them to find available equipment to speed onboarding data into projects.
Discovery runs in two steps: a scan finds sources on the network, and a learn reads the point list from a source you found. Both run on the edge node and take minutes rather than seconds, so both are asynchronous — you queue the operation, then poll for its status.
The methods split along that line:
| Method | Description |
|---|---|
explorer_scan |
Queue a discovery operation on an edge node |
explorer_learn |
Queue a learn to read a source’s point list |
explorer_ops |
Check the status of queued and recent operations |
explorer_sources |
List all discovered sources in the project |
explorer_points |
List the points advertised by a discovered source |
Discovery Workflow
Queue a scan, then poll explorer_ops() until the
operation reaches a terminal state. Wait at least 30
seconds between polls:
import time
# queue a scan and note the returned op id
op_id = client.explorer_scan(op="bacnet-scan")["op_id"]
# poll until done; op.done is True once state is "ok" or "error"
while True:
time.sleep(30)
op = client.explorer_ops().op(op_id)
if op is None or op.done:
break
print(op.state, op.summary) # "ok" "Found 12 sources"An op that no longer appears in the list completed more than 24 hours
ago, so treat a None lookup as done rather than as an
error.
Once the scan completes, read the results from
explorer_sources(). Sources accumulate across scans, so
compare last_scan to identify what the most recent scan
found:
sources = client.explorer_sources()
for s in sources:
print(s.id, s.name, s.vendor, s.model, s.last_scan)To read a source’s points, pass its discovery id to
explorer_learn(). A single explorer_ops() call
covers every outstanding operation, so queue all the learns you need and
poll once:
targets = [s for s in sources if s.model == "VAV-HX1"]
op_ids = {client.explorer_learn(source_id=s.id)["op_id"] for s in targets}
while True:
time.sleep(30)
ops = client.explorer_ops()
if not [o for o in ops if o.id in op_ids and not o.done]:
break
for s in targets:
points = client.explorer_points(source_id=s.id)
print(points.source.name, points.source.point_count)
for p in points:
print(" ", p.addr, p.name, p.unit)The source’s point_count and last_learn
fields are populated at the same time, so
explorer_sources() alone is enough to see which sources
have been learned.
Scan
Queues a discovery operation on an edge node. Returns once the
operation is queued — poll explorer_ops
to track it, then read results from explorer_sources.
explorer_scan(op, node_id=None, **params)Arguments
| Argument | Default | Description |
|---|---|---|
op |
— | Discovery operation to run (required),
e.g. "bacnet-scan" |
node_id |
None |
Serial number of the edge node to run on; defaults to the first node in the project |
**params |
— | Operation specific parameters, see Operations |
List values are joined with commas and bools are sent as
true / false, so
ip_addrs=["10.0.1.1", "10.0.1.2"] and tls=True
are both accepted. Any parameter left unspecified falls back to its
default.
Operations
Each op accepts its own parameters. Credentials are
referenced by name, not ID — the same name shown in
Project Settings.
bacnet-scan — broadcasts across a range of device
instance ids:
| Parameter | Default | Description |
|---|---|---|
port |
47808 |
UDP port to scan |
range_low |
0 |
Lowest device instance id to scan
(0-4194303) |
range_high |
4194303 |
Highest device instance id to scan
(0-4194303) |
bacnet-find — probes an explicit list of addresses. Use
this when broadcast traffic does not reach the devices, across subnets
for example:
| Parameter | Default | Description |
|---|---|---|
ip_addrs |
— | List of IP addresses to probe (required) |
port |
47808 |
UDP port to probe |
max_time |
5min |
Maximum time to spend searching
(1min-15min) |
jasper-scan — scans a Niagara instance for Jasper
sources:
| Parameter | Default | Description |
|---|---|---|
ip_addr |
— | IP address of the Niagara instance (required) |
credential |
— | Name of credential used to connect (required) |
port |
443 |
TCP port for the Niagara WebService |
tls |
— | Use TLS as a bool |
When tls is not specified it is enabled automatically
for ports ending in 443 (i.e.: 443,
8443, 9443) and disabled otherwise.
kaiterra-find — finds Kaiterra sources by device
identifier:
| Parameter | Default | Description |
|---|---|---|
uuids |
— | List of Kaiterra device UDIDs (required) |
credential |
— | Name of credential used to access device data (required) |
Returns
A dict with the ID of the queued operation:
{"status": "ok", "op_id": "0a1b2c3d4e5f6a7b"}Example
# broadcast scan across the default instance id range
op_id = client.explorer_scan(op="bacnet-scan")["op_id"]
# narrow the range and target a specific node
client.explorer_scan(
op="bacnet-scan",
node_id="NA00000000V1",
range_low=100,
range_high=200)
# probe explicit addresses across a subnet
client.explorer_scan(
op="bacnet-find",
ip_addrs=["10.0.1.1", "10.0.1.2"],
max_time="10min")
# scan a Niagara instance using a named credential
client.explorer_scan(
op="jasper-scan",
ip_addr="10.0.0.5",
credential="niagara-ro",
port=8443)Learn
Queues a learn operation to read the point list from a discovered
source. Returns once the operation is queued — poll explorer_ops to track it, then read results
from explorer_points.
explorer_learn(source_id, node_id=None)Arguments
| Argument | Default | Description |
|---|---|---|
source_id |
— | Discovery ID of the source to learn (required) |
node_id |
None |
Serial number of the edge node to run on; defaults to the node that discovered the source |
Everything else is derived from the source itself — its protocol, address, device id, and any credential used to discover it. Learning a source that has already been learned replaces its previous results.
Returns
A dict with the ID of the queued operation:
{"status": "ok", "op_id": "0a1b2c3d4e5f6a7b"}Example
res = client.explorer_learn(source_id="c1578fe370e4")
print(res["op_id"])Ops
Returns the status of explorer operations in this project: those queued, those running, and those completed in the last 24 hours.
explorer_ops()Returns
ExplorerOpList with the following attribute:
| Attribute | Type | Description |
|---|---|---|
ops |
list[ExplorerOp] |
Operations in the response |
Each ExplorerOp has:
| Attribute | Type | Description |
|---|---|---|
id |
str |
Op ID, as returned by explorer_scan or
explorer_learn |
op |
str |
Operation that was requested, e.g. bacnet-scan |
state |
str |
queued, active, ok, or
error |
node_id |
str |
Serial number of the node running the operation |
source_id |
str |
Source being read; learn operations only |
started |
str |
ISO 8601 timestamp, set once the operation begins |
finished |
str |
ISO 8601 timestamp, set once the operation is done |
summary |
str |
Human-readable result, e.g. "Found 12 sources" |
err_code |
str |
Set only when state is error |
States progress queued → active →
ok or error. Three properties wrap that
check:
| Property | Returns |
|---|---|
op.done |
True if state is ok or
error |
op.ok |
True if state is ok |
op.error |
True if state is error; see
err_code for the reason |
Lookups & Iteration
| Call | Returns |
|---|---|
iter(ops) |
iterate ExplorerOp entries |
ops.op(id) |
an ExplorerOp by op ID, or None |
len(ops) |
number of operations |
ops[i] |
an ExplorerOp by index |
Example
# every queued, active, and recently completed operation
for op in client.explorer_ops():
print(op.id, op.op, op.state, op.summary)
# check one operation
op = client.explorer_ops().op("0a1b2c3d4e5f6a7b")
if op.error:
print("failed:", op.err_code)
# which learns are still outstanding
pending = [o.source_id for o in client.explorer_ops()
if o.op.endswith("-learn") and not o.done]Sources
Returns the sources discovered in this project, with any device metadata the source reported.
explorer_sources()Returns
ExplorerSourceList with the following attribute:
| Attribute | Type | Description |
|---|---|---|
sources |
list[ExplorerSource] |
Discovered sources in the response |
Each ExplorerSource has:
| Attribute | Type | Description |
|---|---|---|
id |
str |
Discovery ID, e.g. "c1578fe370e4" |
name |
str |
Source name |
type |
str |
Protocol type, e.g. bacnet |
addr |
str |
Source address |
device_id |
int |
Device ID if reported |
path |
str |
Source path if reported |
vendor |
str |
Vendor name if reported |
model |
str |
Model name if reported |
version |
str |
Version if reported |
firmware |
str |
Firmware revision if reported |
desc |
str |
Description if reported |
last_scan |
str |
ISO 8601 timestamp of the most recent scan |
last_learn |
str |
ISO 8601 timestamp of the most recent learn, or
None |
point_count |
int |
Number of points discovered, or None until learned |
The device metadata fields are populated from data reported by the
source and are None when the source does not advertise
them.
Lookups & Iteration
| Call | Returns |
|---|---|
iter(sources) |
iterate ExplorerSource entries |
sources.source(id) |
an ExplorerSource by discovery ID, or
None |
len(sources) |
number of sources |
sources[i] |
an ExplorerSource by index |
Example
# everything discovered so far
for s in client.explorer_sources():
print(s.id, s.name, s.type, s.addr)
# which sources have been learned
sources = client.explorer_sources()
learned = [s for s in sources if s.last_learn is not None]
print(len(learned), "of", len(sources), "learned")
# lookup by discovery ID
s = sources.source("c1578fe370e4")
print(s.vendor, s.model, s.firmware)Points
Returns a discovered source together with the points it advertises. Use this to inspect a source’s point list before mapping it into the project.
explorer_points(source_id)Arguments
| Argument | Default | Description |
|---|---|---|
source_id |
— | Discovery ID of the source (required) |
Returns
ExplorerPointList with the following attributes:
| Attribute | Type | Description |
|---|---|---|
points |
list[ExplorerPoint] |
Points advertised by the source |
source |
ExplorerSource |
The source metadata |
Each ExplorerPoint has:
| Attribute | Type | Description |
|---|---|---|
name |
str |
Point name |
addr |
str |
Protocol address, e.g. "ai.1" |
type |
str |
Point type |
unit |
str |
Engineering unit if reported |
enum |
str |
Enum type name if applicable |
desc |
str |
Description if reported |
sample |
Any |
Sample value if reported |
Explorer points are not project points: they have no point ID and are
identified by their protocol addr. The list reflects the
source’s last learn, so it is empty until source.last_learn
is set.
Lookups & Iteration
| Call | Returns |
|---|---|
iter(points) |
iterate ExplorerPoint entries |
points.point(addr) |
an ExplorerPoint by protocol address, or
None |
len(points) |
number of points |
points[i] |
an ExplorerPoint by index |
Example
points = client.explorer_points(source_id="23a769452950")
# source metadata comes back with the point list
print(points.source.name, points.source.last_learn)
for p in points:
print(p.addr, p.name, p.unit)
# lookup by protocol address
p = points.point("ai.1")
print(p.name, p.unit) # "Discharge Air Temperature" "°F"Notes
Poll no faster than every 30 seconds. Discovery
operations run on physical equipment and take minutes, not seconds. A
single explorer_ops() call returns every outstanding
operation, so queueing several learns and polling once is both faster
and cheaper than tracking each one separately. Every request counts
against your monthly API usage — see Rate
Limits.
Queue depth is limited. If the project already has
the maximum number of operations outstanding, explorer_scan
and explorer_learn raise a NovantErr with code
429. Wait for queued work to finish and retry. See Error Handling.
Discovery IDs are not project IDs. Explorer sources
use an opaque discovery ID (i.e.: "c1578fe370e4"). A source
only receives an s.<n> ID once it has been bound into
the project, after which it appears in sources().
Scans do not bind anything. Discovery is read-only
with respect to your project — it tells you what is reachable from the
edge node. Binding sources into the project is a separate step, done
with import_sources and import_source_map.
Operations require a read-write API key.
explorer_scan and explorer_learn raise a
NovantErr with code 403 for read-only keys.
Reading ops, sources, and points works with any key.