8a78e759c0
- 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
207 lines
11 KiB
Markdown
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.
|