Skip to content

Equipment Integration

openwcs-docs-agent edited this page Jun 11, 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: real adapters use synchronous HTTP — they reply COMPLETED/FAILED inline and ignore callbackUrl. The emulator uses async callback (Phase 3b): flow sends a callbackUrl in every task body (built from OPENWCS_FLOW_SELF_BASE_URL); the emulator acks ACCEPTED immediately, runs the task in a background goroutine, and POSTs the terminal result to POST /api/flow/device-tasks/{id}/result. When the device responds ACCEPTED/DISPATCHED, flow leaves the task DISPATCHED until the callback arrives. DeviceClient is the seam, so swapping transports doesn't touch the lifecycle logic. (Async Kafka — device.tasks / device.results — remains a future path for production adapter connectivity.)

Adapters (Go)

Adapter Port Transport Status
conveyor 9091 Raw TCP / OPC-UA (PLC) POST /tasks real-hardware skeleton — FAILED "not connected"
asrs 9096 telegram (shuttle/crane) POST /tasks real-hardware skeleton — FAILED "not connected"
amr-geekplus 9093 Geek+ RCS (REST + WebSocket) POST /tasks real-hardware skeleton — FAILED "not connected"
autostore 9094 AutoStore grid (REST) POST /tasks real-hardware skeleton — FAILED "not connected"

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

flow-orchestrator wiring. Each adapter family must be reachable by flow-orchestrator via an env var — the key is the uppercase family name, the value is the adapter's base URL:

Env var Default (standalone) Compose hostname
OPENWCS_ADAPTER_CONVEYOR http://localhost:9091 http://conveyor-adapter:9091
OPENWCS_ADAPTER_ASRS http://localhost:9096 http://asrs-adapter:9096
OPENWCS_ADAPTER_AMR http://localhost:9093 http://amr-geekplus-adapter:9093
OPENWCS_ADAPTER_AUTOSTORE http://localhost:9094 http://autostore-adapter:9094

Missing an entry causes flow-orchestrator to return 422 "No adapter configured for family: X" when a task is dispatched for that family. All four are set in platform/docker-compose.yml.

When the hardware-emulator flag is ON, flow-orchestrator bypasses all four adapters and routes every family to the single equipment-emulator service instead (see Hardware emulator mode).

Hardware emulator mode

A global flag HARDWARE_EMULATOR_ENABLED (OFF by default) is owned by master-data (GET/POST /api/master-data/emulator, ADMIN-only writes; see Services). An admin flips the mode from Settings → Hardware emulator in the UI.

Routing (Phase 2 — current architecture). When the flag is ON, flow-orchestrator routes every device task to the single equipment-emulator service (Go, port 9097) instead of to the per-family adapters. The emulator handles all four families (ASRS, CONVEYOR, AMR, AUTOSTORE) behind the same POST /tasks contract, using the family field that flow now includes in every request body. Flow reads the flag via HttpEmulatorModeClient, which caches the master-data value for 5 s (lazy TTL refresh) and defaults to OFF on any error. Two new env vars are required on flow-orchestrator:

Env var Default Purpose
OPENWCS_EMULATOR_URL http://localhost:9097 equipment-emulator URL (used when flag is ON)
OPENWCS_MASTER_DATA_BASE_URL http://localhost:8081 Master-data URL for flag reads
OPENWCS_FLOW_SELF_BASE_URL http://localhost:8085 flow-orchestrator's own base URL, sent to the emulator as the async device-result callback target (/api/flow/device-tasks/{id}/result)

equipment-emulator service (Go, stdlib only, port 9097):

  • POST /tasks — accepts an optional callbackUrl field. When set (flow builds it from OPENWCS_FLOW_SELF_BASE_URL): acks ACCEPTED immediately and runs the task asynchronously in a goroutine — sleeps the per-command duration, optionally injects a fault, then POSTs the terminal result back to callbackUrl (best-effort; a failed callback is logged and not retried; flow keeps the task DISPATCHED, which is the honest state). When absent: runs synchronously and returns the terminal result inline. In both paths: validates family + command, sleeps a realistic per-family/command duration, then returns/posts COMPLETED with simulated:true and durationMs (the simulated processing time in milliseconds), or FAILED for an unknown family or command. Default durations: ASRS STORE/RETRIEVE ≈ 900 ms / RELOCATE ≈ 3 000 ms (full shuttle move — pick blocker, travel to target channel, drop), CONVEYOR CONVEY ≈ 600 ms / DIVERT/MERGE ≈ 400 ms, AMR TRANSPORT ≈ 1 200 ms / MOVE ≈ 600 ms, AUTOSTORE BIN_STORE/BIN_RETRIEVE ≈ 800 ms / BIN_RELOCATE ≈ 3 000 ms, SCAN 150 ms (all families), unknown commands 300 ms. OPENWCS_EMULATOR_LATENCY_MS overrides every command with a fixed value (0 = instant; useful in tests and CI). Fault injection (OPENWCS_EMULATOR_FAULT_RATE=N): when N > 0, every Nth task fails deterministically — the result carries "fault": true and the per-family failed tally increments (0 or unset = no faults; also fails live-walk CONVEYs before the first scan). Conveyor speed (OPENWCS_EMULATOR_SPEED_MPS, default 0.5 m/s per ADR-0008): sets the live-walk travel speed; live-tunable via /config speedMps (e.g. curl -XPOST .../config -d '{"speedMps":5}' for faster demos). Families and their accepted commands: ASRS (STORE/RETRIEVE/RELOCATE/SCAN), CONVEYOR (CONVEY/DIVERT/MERGE/SCAN), AMR (TRANSPORT/MOVE/SCAN), AUTOSTORE (BIN_STORE/BIN_RETRIEVE/BIN_RELOCATE/SCAN). RELOCATE/BIN_RELOCATE (ADR-0009 dig-out): flow sends the exact blocker move (from → to slot); the emulator sleeps one shuttle-move latency and reports back — no internal decision-making. Loop recirculation (OPENWCS_EMULATOR_RECIRC_EVERY=N, ADR-0007 Phase 3c-2): when N > 0, every Nth atomic CONVEY task (no entryNode) recirculates the conveyor loop once before diverting to its destination (deterministic counter, 0/unset = none), adding one loop's travel time so arrival order visibly diverges from dispatch order (requirement R2). The CONVEY result payload reports recirculations (count of extra loop passes) and decisions — an ordered list of sorter decision points (RECIRCULATED/DIVERTED events) that flow writes to the HU transport trace (requirement R4). recircEvery does not apply to live-walk CONVEYs (see below — recirculation there emerges from flow's own HOLD decisions). Live conveyor walk (ADR-0008 Phase 3d-2): a CONVEY whose payload carries a non-empty entryNode (plus warehouseId and the async callbackUrl) is executed as real hardware would — the emulator decides nothing itself. Steps: (1) fetch the conveyor graph once (GET {flow}/api/flow/conveyor/topology?warehouseId=); (2) POST a scan at the current node (POST {flow}/api/flow/conveyor/routing-requests {warehouseId, node, barcode}); (3) obey flow's RoutingDecision: ROUTE → travel the next edge at speedMps (edge.cost metres ÷ speed); HOLD → dwell ~1 s and rescan the same node; COMPLETE → arrival callback; NO_ROUTE → retry the same node up to 4 × 500 ms (absorbs the dispatch-transaction race where the route plan commits milliseconds after the task is acked; each retry appends a RESCAN entry to the decisions trail) before the walk fails; EXCEPTION → task FAILED immediately. The flow base URL is derived from the task's own callbackUrl (scheme+host). Result payload adds walked: true, scans, recirculations (= HOLD count), and decisions (per-node ROUTED/HELD/ARRIVED/RESCAN entries) in the same shape flow's trace writer already parses. Safety rails: max 500 scans per walk, 5 s timeout per HTTP call, one retry then FAIL. latencyOverrideMs >= 0 overrides both edge travel and HOLD dwell in ms (0 = instantaneous for tests/demos). CONVEYs without entryNode keep the existing atomic behaviour byte-for-byte.
  • GET/POST /config — reads or updates the live {latencyOverrideMs, faultEvery, recircEvery, speedMps} config without a restart. All fields are optional on POST; omitted fields are left unchanged. Examples: curl -XPOST .../config -d '{"recircEvery":3}', curl -XPOST .../config -d '{"speedMps":5}'. Seeded from env at startup.
  • GET /state — real per-family completed/failed tallies derived from actual task load, plus a liveness heartbeat (ticks, uptimeSeconds, lastHeartbeat). The devices block also includes a walks counter (completed live conveyor walks). The response echoes the live config (latencyOverrideMs, faultEvery, speedMps). Shown on the System info screen.
  • GET /healthz, GET /readyz, GET / — health probes and service info (families, version, commit, buildTime).
  • No flag gate inside the emulator — flow only sends it traffic while the flag is ON. No DB, Kafka, or hardware connection.

Emulator ON: every device task (all families) dispatches to the equipment-emulator; returns COMPLETED with simulated:true. Ideal for evaluation, onboarding, and CI: the whole automation flow runs with zero equipment.

Emulator OFF: flow routes by family to the real per-family adapters. POST /tasks on those adapters currently returns FAILED ("hardware not connected") because no real protocol client exists yet — the documented seam for real TCP/OPC-UA/REST drivers.

Per-family adapters (Phase 2b — complete). Per-adapter emulation code (emumode.go, state.go) has been removed from all four adapters. Each adapter validates the incoming command and returns FAILED "hardware not connected" — the seam for a future real protocol client. GET /state and flag polling (WCS_MASTER_DATA_URL) have been removed; adapters expose only /healthz, /readyz, and GET / (service info, without emulator fields). When the flag is ON, flow-orchestrator routes all device tasks to equipment-emulator; the per-family adapters receive no traffic.

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. Scan trace (ADR-0008 §3d-1): each decide() call also appends a SCANNED row to hu_transport_trace when the barcode belongs to an active induction entry — point = scanned node, decision = routing outcome, toPoint = next hop. Unknown barcodes are answered but never traced; a trace failure is isolated and never breaks the routing answer.

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

  • Routing graph table inspector (RoutingGraphTables): the Routing graph tab in the Automation Topology screen is a read-mostly table inspector. The node/edge graph is a projection generated wholesale by Generate routing — hand-editing those rows is pointless, so the nodes and edges tables are read-only. The tab includes:

    • Summary header: node / edge / loop / controller counts; Reload and Discover buttons; a note pointing operators to Generate routing for structural changes.
    • Nodes table (read-only): code, name, position, loop, controller/address; computed in/out- degree; a dead-end badge on nodes with out-degree 0.
    • Edges table (read-only): from → to, exit code, cost; filterable by node code matching either end.
    • Loops table (editable): operators can configure maxHus, whenFull, and overflowTarget inline — these are policy settings, not projection output.
    • Controllers table (editable): PLCs; full add/edit/remove affordances.
    • Save re-sends the full topology (nodes + edges unchanged, updated loops + controllers) via PUT /api/flow/conveyor/topology — all-or-nothing semantics.
  • 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 Discover button appends observed-but-unconfigured nodes/edges (badged "discovered") to the tables for an admin to review 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 placed on a waypoint registers its nodeCode as an alias for that node. A function point placed mid-section (between waypoints) splits the section into two edges and registers its nodeCode for the new split node — so named PLC scan/divert points can sit anywhere along a run, not only at section endpoints.
  • A closed conveyor (closed = true) has its last waypoint linked back to the first, and the resulting cycle is registered as a capacity loop (the same loop model as hand-authored loops).

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.

GTP node sync (side-effect of projection): as part of every projection, the flow-orchestrator also calls POST /api/gtp/stations/{id}/nodes/sync for each placed GTP workstation whose station_id is set. The workstation's STOCK and ORDER conveyor interactions (roles assigned in the Properties panel) become the station's nodes; the function-point offsetM is carried as inboundDistanceM on each node, which the station inbound queue uses to time emulated tote arrivals. This call is best-effort — a GTP failure never aborts the routing projection. See Goods-to-Person Stations.

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. Exit draw mode via the "Draw sections" button, by double-clicking any waypoint, or by pressing Esc.
  • 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. Click a waypoint to select it — an orange ring and a ✕ affordance appear; press Delete / Backspace or click the ✕ to remove it. Deleting drops the point and every section that touched it, re-indexing the survivors (if fewer than two points remain, the path and sections are cleared entirely). The move is gated behind a small pixel threshold so a plain click only selects without nudging the point. Double-click a waypoint to toggle draw-sections mode anchored at that point — a fast shortcut that replaces the "Draw sections" button + re-anchor click without leaving the canvas. Double-clicking again (or pressing Esc) exits draw mode. The waypoint selection clears on equipment deselect, draw-sections toggle, or canvas click.
  • Function-point palette: the palette bar appears when the level has at least one conveyor or ASRS block. Buttons: SCAN, DIV (divert — see below), 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. INDUCT and DISCHARGE carry an ASRS badge to make their ASRS-only constraint immediately visible. Click a button to create a pending point. The marker follows the cursor with a live snap preview so you can see exactly where the click will land before committing. To place it: click anywhere on the canvas — INDUCT/DISCHARGE snap to the nearest ASRS block edge and drop a 1 m IN or OUT stub; all other types snap to the nearest conveyor centreline at any position along a section (not only at an existing node). Drag the staging marker and release for the same result. Esc cancels. While an ASRS port is pending and the cursor is not over an ASRS edge, an inline hint — "IN/OUT snap to ASRS edges · use FEED to join a conveyor" — floats next to the marker as a placement reminder.
  • 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 direction picker: dropping the DIV chip on a conveyor opens a small anchored popover instead of placing a point immediately. The popover shows three checkboxes — ◀ Left, ▲ Straight, Right ▶ — with the cursor's approach side pre-ticked alongside Straight. At least 2 directions must be checked (OK is disabled otherwise). Straight may be unchecked only at the physical end of the line (no outgoing section from that vertex); unchecking it mid-line shows an inline validation error. Pressing OK (or Esc / ✕ / Cancel to discard) materialises one DIVERT_LEFT or DIVERT_RIGHT function point and a 1 m perpendicular stub per checked side. Straight itself creates nothing — the main line continues unmodified. Each divert function point inserts a new junction (splitting [a, b] into [a, m] and [m, b]) unless the drop projects within 0.12 m of an existing endpoint, in which case that point is reused. 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 and the routing graph connects the two automatically. Deleting a function point whose type includes a divert removes the correct branch even when both a left and right stub share the same junction (the side field disambiguates).
  • 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