The API is designed to manage a structure of devices. The devices could be hierarchical if necessary. The structure is organized as follows:
- Devices: There can be many devices. They can be nested. Currently, this api is only two levels deep, for example,
device-0anddevice-1, but the structure could go deeper in the future.
The AsyncAPI v3 spec generated by this service is the single contract consumed by ems-industrial-gateway, ems-line-controller, and ems-hmi. Topic shape, payload schemas, unit vocabulary, naming, and versioning are governed by ems/topic_structure_adr.md.
rectangle "Device Level 0" as device0 {
rectangle "Device 0A" as d0a
rectangle "Device 0B" as d0b
rectangle "Device 0C" as d0c
}
rectangle "Device Level 1" as device1 {
rectangle "Device 1A" as d1a
rectangle "Device 1B" as d1b
rectangle "Device 1C" as d1c
rectangle "Device 1D" as d1d
}
d0a -d-> d1a
d0a -d-> d1b
d0b -d-> d1c
d0c -d-> d1d
note right of device0
Parent devices
Accessed via /devices-0/:device0Id
end note
note right of device1
Child devices
Accessed via /devices-0/:device0Id/devices-1/:device1Id
end noteparticipant client
participant device_api
database typeorm_db
client -> device_api: POST /topology
device_api -> typeorm_db: save device sitemap
device_api -> device_api: generate AsyncAPI v3 spec\n(topics + schemas + x-* protocol bindings)
device_api -> typeorm_db: save spec
device_api -> client: topology createdparticipant device_api
participant industrial_gateway
participant ems_hmi
device_api -> industrial_gateway: GET /asyncapi\n(topics + x-modbus, x-snmp, x-connection)
device_api -> ems_hmi: GET /asyncapi\n(topics + message schemas)
device_api -> ems_hmi: GET /topology\n(devices + buses[])GET /asyncapi and GET /topology serve different purposes for the HMI:
GET /asyncapi— messaging contract: measurement/command vocabulary per device, payload schemas,x-severityon enum values,enum:[...]constraint on each enum channel'svaluefield (sourced from the classvalues:keys), poll rates. Used for TypeScript codegen at build time and topic subscription at runtime.GET /topology— device catalog: the full DTM (devices+buses[]). Used to populate the module browser, derive module type for routing (bess_module.*→/modules/bess/:id), and render the SLD frombuses[].
The gateway only needs /asyncapi. The HMI needs both.
The spec serves two purposes at different times:
- Build-time: Message schemas (e.g.
BooleanReading,FloatReading,CommandPayload) are stable across all deployments. The HMI runs@asyncapi/modelinaagainst the spec to generate TypeScript interfaces. These are compiled into the app. The gateway does the same for Rust structs. - Runtime: Topic paths (e.g.
arcnode/{deployment_uuid}/{module_id}/{device_uuid}) are deployment-specific — they depend on which modules and devices are in the DTM. These cannot be compiled in. Both gateway and HMI fetchGET /asyncapiat startup and resolve topic paths dynamically.
Message types are compiled. Topic paths are fetched.
When the topology changes at runtime (device added, sensor goes offline, DTM re-provisioned), device-api publishes to system/topology-changed. Both the gateway and HMI subscribe to this topic — on receipt they re-fetch GET /asyncapi and diff against their current subscriptions.
The API provides several endpoints to manage this structure:
- POST /topology: Accepts the full DTM (
devices+buses[]), persists it, and regenerates the AsyncAPI v3 spec. Called byplatform-apiat delivery time and on release rollout — not by the HMI. - GET /topology: Returns the full DTM —
devices(all device-0 and device-1 instances with class and display name) andbuses[](electrical bus topology). The HMI's primary source for the module browser and SLD rendering. Module type is derived from theclassfield:bess_module.*→ BESS,compute_module.*→ Compute,grid_module.*→ Grid. - GET /asyncapi: Returns the generated AsyncAPI v3 spec. Consumed by
ems-industrial-gateway,ems-line-controller, andems-hmi. - Devices-0: Devices at level 0 can be accessed at the
/devices-0/:device0Idendpoint. - Devices-1: Devices at level 1 can be accessed at the
/devices-0/:device0Id/devices-1/device1Idendpoint. In this scenariodevice0Idis the parent device anddevice1Idis the child device.