Novant

Documentation

Sign in

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 queuedactiveok 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.