Skip to content

Equipment Integration

openwcs-docs-agent edited this page Jun 5, 2026 · 52 revisions

Equipment Integration

openWCS drives material-handling equipment through a uniform internal device contract (build.md §8), so the rest of the system is decoupled from any specific PLC/robot protocol.

process / API ─► flow-orchestrator ─(device task by family)─► adapter ─► equipment
                                    ◄─────── result ───────────┘

Uniform device contract

  • flow-orchestrator (port 8085) records a device task through the lifecycle REQUESTED → DISPATCHED → COMPLETED/FAILED in its flow schema, and routes it to the right adapter by equipment family (CONVEYOR, ASRS, AMR, AUTOSTORE).
  • POST /api/flow/device-tasks dispatches a task; GET /{id} and GET ?correlationId= read them. RBAC: DEVICE_OPERATE to dispatch, DEVICE_VIEW to read.
  • A failed adapter call records the task FAILED (never lost), surfaced as 502.
  • Transport: synchronous HTTP today (simulator-friendly); the production target is asynchronous Kafka (device.tasks / device.results). DeviceClient is the seam, so swapping transports doesn't touch the lifecycle logic.

Adapters (Go)

Adapter Port Transport Status
conveyor 9091 Raw TCP / OPC-UA (PLC) POST /tasks simulator (CONVEY/DIVERT/MERGE/SCAN)
asrs 9096 telegram (shuttle/crane) scaffold
amr-geekplus 9093 Geek+ RCS (REST + WebSocket) scaffold
autostore 9094 AutoStore grid (REST) scaffold

Adapters are stdlib-first Go services exposing /healthz + /readyz, implementing the device contract. They are independent Go modules (built with go build, not part of the Gradle build).

Not yet wired: a BPMN process-engine that originates device tasks for goods-in / outbound; today tasks are driven directly via the API.

Conveyor routing

Conveyors from different vendors speak different protocols, but they share one pattern: at nodes (scan/decision points) the hardware scans a handling unit and asks the WCS where next? The Go adapter normalizes each vendor's protocol into one contract; flow-orchestrator decides the routing. (/api/flow/conveyor.)

  • Topology is a directed graph: nodes (each with a code the hardware sends, a hardwareAddress to reach the equipment, and posX/posY for the schematic editor) and edges (segments labelled with the exitCode the hardware applies to traverse them, plus a routing cost). Loaded/saved as a whole graph — GET/PUT /conveyor/topology — which backs the admin schematic editor.

  • Route plan: a handling unit carries an ordered list of target node codes (POST /conveyor/routes {barcode, targets:[…]}).

  • On a scan (POST /conveyor/routing-requests {node, barcode}): the WCS finds the HU's current target, computes the next hop toward it by shortest path (Dijkstra, recomputed per scan so topology edits reroute automatically), and replies {action: ROUTE, exitCode, toNode}. As each target is reached the plan advances; the final target → COMPLETE. Unknown node / unreachable target → EXCEPTION; unknown barcode → NO_ROUTE.

  • Loop capacity: a node can belong to a named loop with a max handling-unit count. When a scan would route an HU into a loop that is at capacity, the WCS either HOLDs it (wait upstream, re-evaluated next scan) or diverts it to the loop's OVERFLOW target — configurable per loop. Occupancy = active route plans whose last-scanned node is in the loop.

  • Admin schematic editor: the ui app (React Flow) loads/saves the whole graph — drag nodes to position them, draw edges (with their exit/decision code), set each node's hardware address, and define loops. Backed by GET/PUT /api/flow/conveyor/topology.

  • Topology learning: a sniffer posts observed scans (POST /conveyor/observations {node, barcode, sourceIp}); the WCS infers a candidate topology — nodes seen, segments (consecutive scans of the same HU), and likely targets (terminal nodes) — flagged against the configured graph (GET /conveyor/discovery). The editor's Discover button pulls the new (observed-but-unconfigured) nodes/edges onto the canvas for an admin to confirm and save.

The capture front-end is the conveyor-sniffer adapter (Go, port 9095): it ingests scan telegrams from the defined source IPs (an allowlist + a pluggable per-vendor decoder) and posts them as observations. Today it ingests a controller telegram stream over TCP; a passive libpcap mirror-port tap is a drop-in source later (same Decoder/Forwarder seam) — the same adapter pattern as the device drivers.

Automation Topology (physical layout)

The Automation Topology placement API (/api/flow/automation/topology) is the physical layout model behind the built-in 3D layout editor (tabbed with the conveyor routing graph under the Automation Topology screen). It describes a warehouse as four linked entity types, all scoped per warehouseId:

Entity Tables What it holds
WarehouseLevel flow.warehouse_level Floor number, optional name, elevation in metres
PlacedEquipment flow.placed_equipment A master-data equipment item (or a GTP workstation) placed on a level — x/y/z position (metres), yaw rotation + tilt (degrees), physical envelope length × width × height. Conveyor-family equipment may also carry a path (jsonb [[x,z],…] centreline waypoints in metres) and a closed boolean (loop back from last to first). The category column (conveyor / asrs / sorter / manual-storage / workstation / other) is denormalised on save so the routing projection can classify equipment without an equipment-library round-trip. A workstation placement carries a station_id (uuid, nullable) — a soft reference to the GTP station (gtp_station) it represents; no FK because gtp is a separate service. All other equipment categories leave station_id null.
EquipmentConnection flow.equipment_connection Directed link between two placed pieces, optionally anchored at source/target function points; describes how handling units flow
EquipmentFunctionPoint flow.equipment_function_point A process point on a placed piece: functionType is a comma-separated set of one or more function names (e.g. "SCAN,DIVERT_LEFT") — a single point can combine functions; the backend stores the string as-is. Types: SCAN / DIVERT_LEFT/RIGHT / INDUCT / DISCHARGE / INFEED / DWS / …; offset along the equipment, optional side, and an optional nodeCode that ties the point to a conveyor routing node

API: GET /api/flow/automation/topology?warehouseId= loads the full graph; PUT replaces it. Save accepts client-assigned temp-ids and remaps all internal cross-references to the persisted ids in the returned graph, so the client can reference levels/equipment/function-points within a single payload without a round-trip. RBAC: DEVICE_VIEW on read, DEVICE_OPERATE on write.

The nodeCode on a function point links the placement model to the conveyor routing graph — a discharge point on a placed piece of equipment can correspond directly to a conveyor routing node, so both models stay in sync as topology is updated.

Routing-graph projection (POST /api/flow/automation/topology/project?warehouseId): once the placement model is complete, a single API call (or the Generate routing button in the 3D editor) runs RoutingProjectionService and writes a full-replace conveyor routing graph from it. Projection rules:

  • One ConveyorNode per path point used by a section (box conveyors → 2-node pair; racks/sorters → single node).
  • One directed ConveyorEdge per section — cost = section length in metres; exitCode = target node.
  • Connections are auto-inferred from geometry: when a node of one piece of equipment sits within 1.5 m of a node of another, the projection creates a bidirectional inter-equipment edge automatically (only the closest node pair per equipment pair is linked; existing edges are never duplicated). Explicit role-interaction connections (e.g. GTP workstation function-point links) still produce edges as before.
  • A function point's nodeCode is registered as an alias for the nearest path-point node (the named-PLC-node seam for scan-and-route calls).

The 3D editor's Generate routing button saves the topology first, runs the projection, then reports the resulting node and edge counts. After projection the Routing graph tab reflects the generated graph and no longer needs to be hand-maintained.

Editor views — 3D and 2D plan toggle: The automation topology screen now has a 3D / 2D toggle in the toolbar. Both views operate on the same shared state (placed equipment, conveyor sections, selection) — any edit made in one view is immediately reflected in the other.

3D editor UX: The 3D view loads inside AutomationTopology3D (react-three-fiber + drei). Camera navigation uses OrbitControls with explicit mouse-button mapping (left-drag = orbit, right-drag = pan, scroll = zoom), min/max distance limits, and a max polar angle that prevents going below the floor. An on-screen hint overlay ("Drag to orbit · right-drag to pan · scroll to zoom") makes the controls discoverable without any tutorial. A ▴/▾ fold toggle in the toolbar collapses the page title and level-name/elevation meta row, giving the canvas 90 vh of height for more drawing room; the choice is persisted to localStorage (topoChromeCollapsed) so the editor reopens the way the user left it. Physical equipment connections are no longer drawn as lines or arrows — they are auto-inferred from geometry by the routing projection: any two equipment nodes within 1.5 m of each other are automatically linked in the routing graph. The manual Connect button has been removed.

2D plan editor (PlanEditor2D): An SVG-based top-down view of the active level — easier for laying out equipment and drawing conveyor runs precisely. Key capabilities:

  • Grid + snap: selectable step (0.25 / 0.5 / 1 m) and a snap toggle. Snapping is applied to equipment moves and to conveyor section draw clicks so placements stay aligned.
  • Pan / zoom: scroll to zoom; drag background to pan. Coordinate system matches the 3D world (X right, Z down, rotationDeg = yaw about Y).
  • Place / drag / rotate equipment: selected equipment can be dragged to any grid-snapped position; a quick-rotate button applies 90° yaw steps.
  • Draw conveyor sections: while "Draw sections" mode is active, each grid-snapped click calls the same drawSectionAt callback the 3D editor uses — building the same sections array and path waypoints in the data model. A live hover preview tracks the cursor: a dot marks exactly where the next click will land — lime when the point snaps onto the selected conveyor's centreline (the section will connect), amber for a free grid point. When an anchor exists, a dashed line from the anchor to the cursor shows the pending section and its start → end direction. The snap calculation (drawSnapAt) is shared between the preview and the actual click, so the preview is always accurate.
  • Direction chevrons and decision points: each section is rendered with small, semi-transparent per-metre chevrons (≈ 1 chevron per metre, capped at 30) pointing in the travel direction; waypoints that are the from of two or more sections are rendered in red as automatic decision / divert points.
  • Draggable waypoints: path control points can be dragged independently to adjust conveyor geometry without re-drawing the whole run.
  • Function-point palette: the palette bar appears when the level has at least one conveyor or ASRS block. Buttons: SCAN, DIVERT_LEFT, DIVERT_RIGHT, DWS, QUERY_POINT, WRAPPER, LABEL_APPLICATOR, INDUCT, DISCHARGE, and INFEED. Each button is enabled only when its drop target exists — INDUCT/DISCHARGE need an ASRS block; every other type needs a conveyor. Click a button to create a pending point. To place it: click anywhere on the canvas — the point snaps to the nearest conveyor centreline at any position along a section (not only at an existing node) — or drag the staging marker onto the target and release. Esc cancels.
  • Combined functions per point: after placing a point, the functions panel shows a row of per-function chips (one per available type). Each chip can be toggled on or off — a point can carry any combination of functions (e.g. SCAN + DIVERT_LEFT on one physical point). At least one function must remain active. The functionType field stores the active set as a comma-separated string (e.g. "SCAN,DIVERT_LEFT"). The marker colour and label reflect the combined set: a divert function takes precedence (red), then the first port colour, else the first function's colour; the label shows all active short names joined by · (e.g. "SCAN · ◀ DIV").
  • Function-point markers (2D): dropped points render as small colour-coded shapes nudged to the LEFT or RIGHT side of the conveyor centreline (for divert types) with a short stem back to the centreline. DIVERT_LEFT and DIVERT_RIGHT render in red; INDUCT, DISCHARGE and INFEED (port / merge types) render as diamonds rotated 45°; all others use the per-type colour from functionColor(). The same diamond shape is used for the corresponding 3D octahedron marker. Clicking a marker selects the owning equipment. A placed marker can be dragged along the centreline — the point moves live (updating its offsetM) and rebinds to a different conveyor when the cursor crosses onto one.
  • Divert drops start branch drawing: dropping a DIVERT_LEFT or DIVERT_RIGHT always inserts a new junction (splitting the conveyor section [a, b] into [a, m] and [m, b]) unless the drop projects within 0.12 m of an existing endpoint — at that distance the existing point is reused. The junction becomes the draw anchor and the editor immediately enters draw-sections mode so the next clicks lay the divert branch. When the resulting 1 m stub's free endpoint falls within 0.75 m of another conveyor's centreline, the editor snaps it exactly onto that line — the divert meets the existing conveyor at one shared coordinate rather than overlaying an offset parallel stub. The routing-graph projection's adjacency-inference then connects the two across that shared point automatically.
  • INFEED (conveyor feeder merge): dropping an INFEED on a conveyor works exactly like a divert but with the directed section reversed — the stub section runs into the junction rather than out of it (branch → junction instead of junction → branch). Extend the stub back to its source and link. The INFEED marker renders as a teal diamond (⇥ FEED).
  • ASRS IN/OUT port stubs: INDUCT and DISCHARGE function points snap to an ASRS block's footprint edge rather than a conveyor centreline. Dropping either creates a 1 m conveyor stub owned by the ASRS (stored in the ASRS's own path/sections fields): INDUCT flows toward the rack (stub → port); DISCHARGE flows away (port → stub). The stub renders as a conveyor line coming out of the ASRS body in both 2D and 3D, is extendable with the normal Draw sections / waypoint-drag tools; physical connections to neighbouring conveyors are auto-inferred from geometry. ASRS blocks start with no ports; they are added from the function-point palette one at a time.
  • The library panel and properties panel are shared between the 3D and 2D views.

GTP workplaces in the equipment library: The library panel includes a "GTP workplaces" section (visible when the warehouse has at least one GTP station) that lists every gtp_station by code and name. Clicking + Add drops a workstation box (1.2 × 1.2 × 1.0 m, green rounded box in 3D / green box in 2D) linked to that station via station_id. Each entry shows a placed N / not placed badge so admins can see at a glance which stations are already on the canvas. The stationId binding can be changed after placement via the GTP workplace Select in the properties panel. Workstations are movable like any other equipment; physical links to conveyors are auto-inferred from geometry (the manual Connect tool has been removed). They are not conveyors, so path/section tools are not available for them. Selecting a placed workstation also reveals a Conveyor interactions panel in the properties sidebar — add one or more point-level connections to specific conveyor function points, each tagged with a role:

  • STOCK — the stock-feed conveyor (e.g. from an ASRS) that delivers stock HUs to this station
  • ORDER — the order conveyor/destination that carries away completed order HUs
  • DECANT — a decant point (for DECANTING-mode stations)

A pick-place station typically has two interactions (STOCK + ORDER); a decant station one or none. Each interaction records a connection from the workstation placement to the function point (fromPlacedId / toPointId / label = role) using the standard AutomationConnection model. Interactions can be removed individually with the Remove button.

Conveyor polylines: Equipment whose library family is CONVEYOR can have a centreline path instead of a single straight box. When a conveyor has a usable path (≥ 2 waypoints), the 3D editor renders it as a chain of oriented box segments connecting the waypoints; the closing segment is added automatically when closed = true (for recirculating sorter loops). While the conveyor is selected:

  • Draggable sphere handles appear at each waypoint and track pointer movement on a horizontal plane — OrbitControls are disabled for the duration of the drag so the camera stays put.
  • The properties panel shows a Conveyor path section with: Draw path (toggles a mode where each left-click on the floor appends a new waypoint), Start path from box (seeds a two-point path from the current box position + rotation + length), Closed loop checkbox, Remove last, and Clear. The hint overlay switches to "Draw mode: click the floor to add conveyor waypoints" while draw mode is active.
  • The Length (m) field is hidden when a path is active (length is implicit from the geometry).
  • Non-conveyor equipment continues to render as a single box with a drag-to-move gizmo.

Related: Services · Architecture · Outbound Flow · Roadmap and Status.

Clone this wiki locally