# Delmia / DNC Integration API — document download & WW recipe-download notification Reference for the **`delmiaintegration`** solution (`~/Desktop/delmiaintegration`), the legacy bridge that pulls "proven" manufacturing documents (NC programs / recipes) out of **DELMIA Apriso** and notifies **AVEVA Wonderware** that a recipe was downloaded for a machine. The recipe/NC-program push to machines is classic **DNC** (Distributed Numerical Control), hence this lives under `former-api-specs/dnc/`. There are **two distinct API surfaces** in this solution: | # | Surface | Direction | Transport | |---|---------|-----------|-----------| | A | **Delmia document web service** | integration → Delmia (consumed) | form-urlencoded POST, **XML** response | | B | **WW recipe-download notification** | `WWNotifier` CLI → Wonderware receiver | **JSON** POST (`/notify`) | > Surface B is the **legacy predecessor of the ScadaBridge `DelmiaRecipeDownload` inbound method** — > same field set and result shape (see *ScadaBridge equivalent* below). Source files (under `~/Desktop/delmiaintegration`): - `DelmiaIntegration/DelmiaClient.cs` — Delmia HTTP client (Surface A) - `DelmiaContracts/*.cs` — XML contracts (`DownloadResult`, `SearchResults`/`SearchResult`, + unused `MachineInfo`/`MachineSearchResults`/`UserInfo`) - `WWNotifier/Program.cs`, `WWNotifier/CommandLineOptions.cs`, `WWNotifier/Models/RecipeDownload*.cs` — the CLI notifier (Surface B) - `WWNotifier/App.config` — `NotifyURL` / `NotifyTimeout` ### End-to-end flow ``` DELMIA Apriso ──(A) RequestProvenDocument / RequestDocument / Search──► DelmiaClient (intercim "ruleset" web service, XML) │ downloads proven doc/recipe to a path ▼ WWNotifier.exe ──(B) POST /notify {RecipeDownload}──► Wonderware "WW receiver" (CLI, exit code + YES/NO) ◄── {RecipeDownloadResult} ── ``` --- ## Surface A — Delmia document web service (consumed by `DelmiaClient`) `DelmiaClient` (`DelmiaClient.cs`) is a thin `HttpClient` wrapper. The **base URL is supplied by the caller** (constructor / `URL` property — there is no default in `DelmiaIntegration/App.config`); `Timeout` defaults to **30 s**. Every call is an `application/x-www-form-urlencoded` POST to `{URL}/`, and every response is **XML** deserialized with `XmlSerializer` (root namespace `http://intercim.com/ruleset` — the Apriso/InterCIM heritage). | Verb | Path | Form fields | Response (XML) | Client method | |------|------|-------------|----------------|---------------| | `POST` | `{URL}/RequestProvenDocument` | `username`, `machineID`, `partNumber`, `operationNumber`, `workOrderNumber` | `DownloadResult` | `RequestProvenDocument[Async]` | | `POST` | `{URL}/RequestDocument` | + `documentKey` | `DownloadResult` | `RequestDocument[Async]` | | `POST` | `{URL}/Search` | `username`, `machineID`, `partNumber`, `operationNumber` | `SearchResults` | `Search[Async]` | - **`RequestProvenDocument`** — fetch the single *proven* (released/approved) document for the part + operation, logging it against `username` / `workOrderNumber`. - **`Search`** — list candidate documents matching the part/operation (each carries a `DocumentKey` + `DocumentURL`). - **`RequestDocument`** — fetch one specific document chosen from a search by `documentKey`. `username` here is **identity/audit only** — it is a form field, not an authentication credential. There is **no API key, no `Authorization` header, no TLS requirement** on this surface (see gotchas). ### Response — `DownloadResult` (metadata about the download) `DownloadResult` describes *who/what/which order+document* was downloaded — it does **not** carry the file bytes (the file lands at a download path on the Delmia side). Fields: | Group | Fields | |-------|--------| | User | `UserKey` (int), `UserName`, `UserSite` | | Machine | `MachineKey` (int), `MachineID`, `MachineSite` | | Order | `WorkOrderNumber`, `ShopOrderKey` (int), `ShopOrderID`, `ShopOrderStatus`, `ShopOrderOperKey` (int), `ShopOrderOperID`, `ShopOrderOperStatus` | | Document | `DocumentKey` (int), `DocumentName`, `DocumentRev`, `DocumentStatus` | | Part | `PartID`, `PartRev` | | Outcome | **`TransferSuccessful`** (bool), `ErrorMessage` (string) | > **Success is `TransferSuccessful`, not the HTTP status.** Treat `TransferSuccessful == true` as the > only success signal; `ErrorMessage` carries the reason otherwise. ### Response — `SearchResults` / `SearchResult` `SearchResults` = `{ List Results; string ErrorMessage }`. Each `SearchResult`: `ShopOrderKey`/`ShopOrderID`/`ShopOrderStatus`, `ShopOrderOperKey`/`ShopOrderOperID`/`ShopOrderOperStatus`, `DocumentKey` (int), `DocumentObjectID` (int, with an `XmlIgnore` `…Specified` flag), `DocumentName`, `DocumentRev`, `DocumentStatus`, **`DocumentURL`**, `PartID`, `PartRev`. ### Quick reference (curl, Surface A) ```bash # Search for candidate documents (values from DownloadTestUtil's example) curl -X POST "{URL}/Search" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "username=dohertj2" \ --data-urlencode "machineID=000005" \ --data-urlencode "partNumber=00444455599" \ --data-urlencode "operationNumber=0100" # -> # Request the proven document for that part/operation curl -X POST "{URL}/RequestProvenDocument" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "username=dohertj2" \ --data-urlencode "machineID=000005" \ --data-urlencode "partNumber=00444455599" \ --data-urlencode "operationNumber=0100" \ --data-urlencode "workOrderNumber=W111111" ``` --- ## Surface B — WW recipe-download notification (`WWNotifier` → WW receiver) `WWNotifier.exe` (`WWNotifier/Program.cs`) is a **command-line tool** invoked by the download process to tell Wonderware that a recipe/NC file was placed at a path for a machine. It POSTs a JSON `RecipeDownload` to the configured receiver and interprets the JSON `RecipeDownloadResult`. ### CLI options (`CommandLineOptions`) | Short | Long | Required | Meaning | |-------|------|----------|---------| | `-d` | `--downloadpath` | **yes** | File download path | | `-m` | `--machine` | **yes** | Machine code | | `-w` | `--workorder` | **yes** | Work order number | | `-p` | `--partnumber` | **yes** | Part / item number | | `-s` | `--seqop` | no | Job step / sequence number | | `-u` | `--username` | no | Operator username | ### Endpoint & payload `POST {NotifyURL}` with a JSON body, expecting a JSON reply. From `WWNotifier/App.config`: `NotifyURL = http://wonder-app-vd01.zmr.zimmer.com:9001/notify`, `NotifyTimeout = 30` (seconds). `NotifyURL` may be a **comma-separated list** — each is tried in order and the **first success wins** (failover). **Request — `RecipeDownload` (JSON)** | Field | Type | Source | |-------|------|--------| | `MachineCode` | string | `--machine` | | `DownloadPath` | string | `--downloadpath` | | `WorkOrderNumber` | string | `--workorder` | | `PartNumber` | string | `--partnumber` | | `JobStepNumber` | string | `--seqop` | | `Username` | string | `--username` | **Response — `RecipeDownloadResult` (JSON)**: `{ "Result": bool, "ResultText": string }`. ### Process contract (stdout + exit code) The caller (Delmia) reads `WWNotifier`'s console output and exit code: - On success → prints **`YES`**; exit code `0`. - On any failure (arg parse, missing config, all receivers failed, `Result == false`) → prints **`NO`** plus a reason line; `Environment.ExitCode = -1`. ```bash WWNotifier.exe -m Z28061 -d "C:\recipes\wo111111.nc" -w W111111 -p P111111 -s 0100 -u chamalas # stdout: YES (or: NO\n) # POSTs {"MachineCode":"Z28061","DownloadPath":"C:\\recipes\\wo111111.nc", … } to /notify ``` --- ## Other contracts (defined but unused) `MachineInfo`, `MachineSearchResults` (`{ List Results; string ErrorMessage }`), and `UserInfo` exist in `DelmiaContracts` (same `intercim.com/ruleset` namespace) but are **not wired to any `DelmiaClient` operation** — scaffolding for machine-lookup / user-lookup endpoints that were never implemented. Shapes, for reference: `MachineInfo` = `MachineKey`/`MachineID`/`MachineName`/ `DownloadPath`/`MachineDescription`/`MachineSite`/`MachineStatus`; `UserInfo` = `UserKey`/`UserName`/ `UserSite`/`IsActive`. --- ## Behavior notes & gotchas - **No authentication on either surface.** Surface A's `username` is just a logged form field; Surface B sends no credential at all. Both are plaintext HTTP. (Contrast the ScadaBridge inbound API, which requires `X-API-Key`/`Bearer` — see `../mes/authgaps.md`.) - **Surface A ignores HTTP status.** `DelmiaClient` never calls `EnsureSuccessStatusCode`; a non-2xx body is handed straight to `XmlSerializer`, which typically throws → the `catch` returns a generic `TransferSuccessful = false` / `"Failed to call Delmia web service at ''."`. The real HTTP error is lost. - **Sync methods block on `.Result`.** `RequestProvenDocument`/`RequestDocument`/`Search` call `.Result` on the async POST (deadlock-prone in some contexts); async variants exist. - **`WWNotifier` has a latent NPE in its error path.** On a notify exception it logs `error.InnerException.Message` (`Program.cs:129`); if `InnerException` is null this throws inside the `catch`, masking the original error. - **`NotifyURL` failover is first-success-wins**, in list order; a slow first endpoint costs up to `NotifyTimeout` before the next is tried. - **Surface A base URL is caller-supplied** (no config default), so the effective Delmia endpoint depends on whoever constructs `DelmiaClient` (e.g. `TestUI`/`DelmiaClientUI`). --- ## ScadaBridge equivalent (porting note) - **Surface B → ScadaBridge Inbound API `DelmiaRecipeDownload`.** The legacy `WWNotifier.exe` + the `/notify` WW receiver are replaced by `POST /api/DelmiaRecipeDownload` (authenticated with `X-API-Key`/`Bearer`). The contract is identical: request `{ MachineCode, DownloadPath, WorkOrderNumber, PartNumber, JobStepNumber, Username }` → response `{ Result, ResultText }`. The inbound script routes to the site via `Route.To(MachineCode).Call("ProcessRecipeDownload", …)`. The CLI's comma-list failover is superseded by Traefik active-node routing; the `YES`/`NO` + exit code contract becomes the HTTP status + JSON body. - **Surface A (Delmia document service) has no direct ScadaBridge equivalent** — retrieving proven documents from DELMIA Apriso remains an external concern (it would be an External System Gateway call if pulled into ScadaBridge). This file documents the **legacy** `delmiaintegration` contracts for reference/parity during that migration.