BACnet discovery via CRUD (step-by-step)

After a blank data model (e.g. you ran the delete-all-sites and reset flow), you can learn in new BACnet devices by driving the API yourself. No new data gets orphaned: create site → equipment (optional) → points with BACnet refs, then push discovery into the graph so Brick and BACnet align.

Use BASE = your API base URL (e.g. http://localhost:8000 or http://192.168.204.16:8000). All requests use accept: application/json and, for POST/PATCH/PUT, content-type: application/json.


1. (Optional) Discover devices — Who-Is

See which BACnet devices respond in an instance range:

curl -X POST "${BASE}/bacnet/whois_range" \
  -H "accept: application/json" \
  -H "content-type: application/json" \
  -d '{"request": {"start_instance": 1, "end_instance": 3456799}}'

Use the gateway url in the body if needed (e.g. "url": "http://192.168.1.50:8080"). Omit to use the server default (e.g. OFDD_BACNET_SERVER_URL or http://localhost:8080). From the response, pick a device_instance (e.g. 3456789) for the next step.


2. Get points for one device — Point discovery (JSON only)

Returns the list of objects for that device. You’ll use this to create points in the DB:

curl -X POST "${BASE}/bacnet/point_discovery" \
  -H "accept: application/json" \
  -H "content-type: application/json" \
  -d '{"instance": {"device_instance": 3456789}}'

Response shape (from diy-bacnet-server): result.data.objects is a list of objects. Each has at least:

  • object_identifier — e.g. "analog-input,1", "device,3456789"
  • object_name or name — human-readable name

Skip the device object when creating points; use analog-input, binary-value, etc. Save the device_instance and the objects list; you’ll use them in step 4.


3. Push device into the graph (Brick + BACnet in TTL)

So the in-memory graph and config/data_model.ttl contain this device’s BACnet RDF:

curl -X POST "${BASE}/bacnet/point_discovery_to_graph" \
  -H "accept: application/json" \
  -H "content-type: application/json" \
  -d '{
    "instance": {"device_instance": 3456789},
    "update_graph": true,
    "write_file": true
  }'

You can do this before or after creating sites/points in the DB. Doing it before lets you inspect the TTL; doing it after keeps Brick (from DB) and BACnet (from discovery) in sync after your CRUD.


4. CRUD: Create site

curl -X POST "${BASE}/sites" \
  -H "accept: application/json" \
  -H "content-type: application/json" \
  -d '{"name": "MyBuilding", "description": "Main campus"}'

From the response, copy idSITE_ID.


5. (Optional) CRUD: Create equipment

curl -X POST "${BASE}/equipment" \
  -H "accept: application/json" \
  -H "content-type: application/json" \
  -d '{"site_id": "'"${SITE_ID}"'", "name": "AHU-1", "description": "Air handler"}'

Copy idEQUIPMENT_ID. If you skip equipment, omit equipment_id when creating points.


6. CRUD: Create points (one per BACnet object)

For each object from step 2 (excluding the device,... object), create a point with BACnet addressing so the scraper and FDD can use it:

curl -X POST "${BASE}/points" \
  -H "accept: application/json" \
  -H "content-type: application/json" \
  -d '{
    "site_id": "'"${SITE_ID}"'",
    "external_id": "ZoneTemp",
    "bacnet_device_id": "3456789",
    "object_identifier": "analog-input,1",
    "object_name": "Zone Temperature",
    "equipment_id": "'"${EQUIPMENT_ID}"'"
  }'
  • site_id (required): UUID from step 4.
  • external_id (required): unique per point; used as the time-series key (e.g. ZoneTemp, or the BACnet object_name).
  • bacnet_device_id: device instance as string (e.g. "3456789").
  • object_identifier: e.g. "analog-input,1".
  • object_name: optional but useful for display.
  • equipment_id: optional; use if you created equipment in step 5.

Repeat for each object you want to scrape. You can add brick_type and fdd_input (rule_input) now or later via PATCH /points/{id} or GET /data-model/export → edit → PUT /data-model/import.


7. (Optional) Tag Brick / rule_input in bulk

  • GET /data-model/export?site_id=… — list points with BACnet refs.
  • Edit the JSON (set brick_type, rule_input per point).
  • PUT /data-model/import with the edited list (use point_id from export). BACnet refs are preserved.

8. Verify

  • GET /sites — list sites.
  • GET /points?site_id=… — list points; check bacnet_device_id and object_identifier.
  • GET /data-model/check — triple count, orphan count, sites/devices.
  • GET /data-model/ttl — full TTL (Brick + BACnet).

Then run the BACnet scraper; it will read only points that have bacnet_device_id and object_identifier set.