Files
ScadaBridge/docs/former-api-specs/dnc/Delmia-Integration-API.md
T
Joseph Doherty 8a78e759c0 docs: former-api-specs (MES + DNC/Delmia) + inbound compile-error known issue
- former-api-specs/mes: Alarm-API, MoveIn-MoveOut-API, API-key authgaps (from ~/Desktop/mesapi)
- former-api-specs/dnc: Delmia-Integration-API — Delmia document service + WW recipe-download notify (from ~/Desktop/delmiaintegration)
- known-issues: inbound API compile error not client-visible; no api-method validate
2026-06-26 04:13:19 -04:00

207 lines
11 KiB
Markdown

# 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}/<Operation>`, 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<SearchResult> 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"
# -> <SearchResults xmlns="http://intercim.com/ruleset"> … </SearchResults>
# 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<reason>)
# POSTs {"MachineCode":"Z28061","DownloadPath":"C:\\recipes\\wo111111.nc", … } to /notify
```
---
## Other contracts (defined but unused)
`MachineInfo`, `MachineSearchResults` (`{ List<MachineInfo> 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 '<URL>'."`. 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.