Files
ScadaBridge/docs/former-api-specs/mes/MoveIn-MoveOut-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

340 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WWSupport MES API — Move In / Move Out API
Reference for the batch **move-in / move-out** endpoints exposed by the **WWSupport / APIServer**
ServiceStack service. These endpoints hand a container/work-order payload to a machine's
`MesReceiver` object in AVEVA Wonderware (System Platform / Galaxy) over MXAccess, using a
flag-based request/response handshake, and read back the resulting batch id (and, for move-out,
recorded cycle data from SQL).
- **Service host:** `AppHost` (`AppSelfHostBase`, name `"APIServer"`) — `APIServer/APIServer/AppHost.cs`
- **Service implementation:** `MesServices``APIServer/APIServer.ServiceInterface/MesServices.cs`
- **Business logic:** `MesNotifier``APIServer/APIServer.ServiceInterface/MesNotifier.cs`
- **Framework:** ServiceStack 6.0.2, self-hosted; SQL Server (OrmLite, `SqlServer2016Dialect`)
> Serialization note: `JsConfig.IncludeNullValues = true`, so null fields **are** emitted in JSON
> responses (e.g. `"BatchID": null`).
This document covers `/mes/movein` and `/mes/moveout`. The two alarm endpoints
(`/mes/alarmstatus`, `/mes/simplealarmstatus`) are documented in [`Alarm-API.md`](Alarm-API.md).
---
## Endpoints
| Verb | Route | Request DTO | Response DTO |
|------|-------|-------------|--------------|
| `POST` | `/mes/movein` | `MoveInRequest` | `MoveInResponse` |
| `POST` | `/mes/moveout` | `MoveOutRequest` | `MoveOutResponse` |
Both are dispatched through ServiceStack `Any(...)` handlers in `MesServices`, which resolve the
singleton `MesNotifier` and call `MoveIn(...)` / `MoveOut(...)`.
> The handlers call the async business methods with a blocking `.Result`
> (`mesNotifier.MoveIn(request).Result`), so each request occupies its thread until the
> handshake completes or the 30 s timeout fires.
The service also enables `PostmanFeature` and `OpenApiFeature` (Swagger), so a running instance
exposes a browsable contract and a Postman collection.
---
## Authentication & authorization
Identical to the alarm endpoints — all MES services are decorated with:
```csharp
[Authenticate]
[RequiredRole("MESAPI")]
public class MesServices : Service { ... }
```
The caller must be authenticated **and** hold the `MESAPI` role. Configured in `AppHost.Configure`:
- **API key** — `ApiKeyAuthProvider` (`SessionCacheDuration = 30 min`, `RequireSecureConnection = false`).
An API key whose `Environment == "test"` is routed to the `TestDb` connection instead of the
production DB (`AppHost.GetDbConnection`). **Caveat:** that redirect only applies to OrmLite
lookups — the move-out cycle-data read uses a raw `ConnectionStrings["BatchDB"]` connection and
is *not* affected by the test-key redirect (see gotchas).
- **LDAP** — `LdapAuthProvider`.
- `AllowGetAuthenticateRequests = true`.
Default API-key transport is the standard `Authorization` header (`Bearer <key>`, or HTTP Basic
with the key as username); `?apikey=<key>` also works since `AllowInHttpParams` defaults on.
---
## `POST /mes/movein`
Hands a container + its work orders to a machine's `MesReceiver` to start/stage a batch.
### Request — `MoveInRequest`
| Field | Type | Notes |
|-------|------|-------|
| `SAPID` | string | SAP identifier of the target machine. Used to resolve the `Machine` row. Required. |
| `OperatorName` | string | Operator initiating the move-in. |
| `JobSequenceNumber` | string | Job sequence number. |
| `ContainerNumber` | string | MES container number. |
| `WorkOrders` | `WorkOrderInfo[]` | Work orders being moved in (default empty). Each item: `WorkOrderNumber` (string), `PartNumber` (string). |
```json
{
"SAPID": "100012345",
"OperatorName": "chamalas",
"JobSequenceNumber": "50",
"ContainerNumber": "cont-012",
"WorkOrders": [
{ "WorkOrderNumber": "W111111", "PartNumber": "P111111" }
]
}
```
### Response — `MoveInResponse`
| Field | Type | Notes |
|-------|------|-------|
| `WasSuccessful` | bool | `true` only if the machine reported `MoveInSuccessfulFlag = true`. |
| `ErrorText` | string | Failure reason, or the machine's `MoveInErrorText` on a completed-but-failed move. |
| `BatchID` | int? | The machine's `MoveInBatchID`, **only when non-zero**; otherwise null. |
### Behavior
1. **30 s budget** (`CancellationTokenSource(30000)`).
2. **Resolve machine:** `db.Single<Machine>(x => x.SAPID == request.SAPID)`. If not found →
`WasSuccessful = false`, `ErrorText = "Failed to find machine with SAPID '<id>'"` (early return).
3. **Subscribe** to the machine's move-in tagset (`MesMoveInTagset`, all under
`{Machine.Code}.MesReceiver.*`) via `AdviseSupervisory`; wait for the first value of each.
If any subscription fails → `"Failed to connect to machine"`.
4. **Ready gate:** require `MoveInReadyFlag == true`, else `"Machine move in ready flag not set to true"`.
5. If still successful, perform the **handshake**:
- Arm a watcher for `MoveInCompleteFlag → true` (`OnValue`).
- Write the payload tags (see table) **and** set `MoveInFlag = true`. If any write is not
acknowledged → `"Failed to write move in information to machine"`.
- Wait for `MoveInCompleteFlag`:
- **Completed:** `WasSuccessful = MoveInSuccessfulFlag`; `ErrorText = MoveInErrorText`;
`BatchID = MoveInBatchID` (only if ≠ 0).
- **Timed out:** `"Timeout waiting for move in information to be processed"`.
6. **Unsubscribe** all tags.
### Tags written / read (`{Machine.Code}.MesReceiver.*`)
| Property | MXAccess tag suffix | Type | Direction | Source / meaning |
|----------|---------------------|------|-----------|------------------|
| `MoveInReadyFlag` | `MoveInReadyFlag` | bool | read (gate) | Machine must be ready to accept a move-in. |
| `MoveInMesContainerNumber` | `MoveInMesContainerNum` | string | write | `request.ContainerNumber`. |
| `MoveInOperatorName` | `MoveInOperatorName` | string | write | `request.OperatorName`. |
| `MoveInJobSequenceNumber` | `MoveInJobSequenceNumber` | string | write | `request.JobSequenceNumber`. |
| `MoveInNumberWorkOrders` | `MoveInNumberWorkOrders` | int | write | `request.WorkOrders.Count`. |
| `MoveInPartNumbers` | `MoveInPartNumbers[]` | string[] | write | `WorkOrders[*].PartNumber`, **fixed length 50**. |
| `MoveInWorkOrderNumbers` | `MoveInWorkOrderNumbers[]` | string[] | write | `WorkOrders[*].WorkOrderNumber`, **fixed length 50**. |
| `MoveInFlag` | `MoveInFlag` | bool | write (trigger) | Set `true` to start processing. |
| `MoveInCompleteFlag` | `MoveInCompleteFlag` | bool | watch | Machine sets `true` when done. |
| `MoveInSuccessfulFlag` | `MoveInSuccessfulFlag` | bool | read (result) | Machine's success verdict. |
| `MoveInErrorText` | `MoveInErrorText` | string | read (result) | Machine's error message. |
| `MoveInBatchID` | `MoveInBatchID` | int | read (result) | Created batch id (0 = none). |
---
## `POST /mes/moveout`
Closes out a container's work orders on a machine's `MesReceiver`, and (when the machine is
configured for it) returns the recorded cycle data for the resulting batch.
### Request — `MoveOutRequest`
Same as `MoveInRequest` **minus `JobSequenceNumber`**:
| Field | Type | Notes |
|-------|------|-------|
| `SAPID` | string | SAP identifier of the target machine. Required. |
| `OperatorName` | string | Operator initiating the move-out. |
| `ContainerNumber` | string | MES container number. |
| `WorkOrders` | `WorkOrderInfo[]` | Work orders being moved out. Each: `WorkOrderNumber`, `PartNumber`. |
```json
{
"SAPID": "100012345",
"OperatorName": "chamalas",
"ContainerNumber": "cont-012",
"WorkOrders": [
{ "WorkOrderNumber": "W111111", "PartNumber": "P111111" }
]
}
```
### Response — `MoveOutResponse`
| Field | Type | Notes |
|-------|------|-------|
| `WasSuccessful` | bool | `true` only if the machine reported `MoveOutSuccessfulFlag = true`. |
| `ErrorText` | string | Failure reason, or the machine's `MoveOutErrorText`. |
| `BatchID` | int? | The machine's `MoveOutBatchID` (non-zero), **and** only when cycle storage is enabled (below). |
| `Data` | `MoveOutData[]` | Recorded cycle values (empty unless cycle storage is enabled). Defaults to `[]`. |
#### `MoveOutData`
| Field | Type | Source |
|-------|------|--------|
| `BatchId` | int | `MachineCycle.MachineBatchId`. |
| `CycleId` | int | `MachineCycle.MachineCycleId`. |
| `ValueName` | string | One of the cycle-data keys (below). |
| `Value` | object | The value for that key (`null` becomes empty string in SQL via `COALESCE`). |
### Behavior
Steps 15 mirror move-in (resolve `Machine` by `SAPID`; subscribe `MesMoveOutTagset`; require
`MoveOutReadyFlag == true`; write payload + `MoveOutFlag = true`; wait for `MoveOutCompleteFlag`).
On completion: `WasSuccessful = MoveOutSuccessfulFlag`, `ErrorText = MoveOutErrorText`.
**Cycle-data read (move-out only).** If `MoveOutBatchID != 0` **and** the machine's `OtherData`
contains the literal `"StoreCycleDataForMES"`:
- `BatchID = MoveOutBatchID`.
- Open a **raw** `SqlConnection` on `ConnectionStrings["BatchDB"]` and run a parameterized query
(`@machineBatchID = MoveOutBatchID`) against `BT.dbo.MachineCycle`, expanding each cycle's
`OtherData` JSON (via `OPENJSON … CROSS APPLY (VALUES …)`) into one `MoveOutData` row per
key/value pair. Only rows where `ISJSON(mc.OtherData) = 1` are processed.
```sql
-- Shape of the cycle-data query (BT.dbo.MachineCycle, WHERE MachineBatchId = @machineBatchID)
SELECT mc.MachineBatchId, mc.MachineCycleId, v.ValueName, COALESCE(v.Value, N'') AS Value
FROM BT.dbo.MachineCycle mc
CROSS APPLY OPENJSON(mc.OtherData) WITH ( /* one column per key below */ ) od
CROSS APPLY (VALUES (N'ProgramNum', od.ProgramNum), /* … one row per key … */ ) AS v(ValueName, Value)
WHERE mc.MachineBatchId = @machineBatchID AND ISJSON(mc.OtherData) = 1;
```
**Cycle-data keys extracted** (19 per cycle): `ProgramNum`, `DewPointStart`, `SegmentStart2`,
`HighVacEndSeg1`, `SegmentStart3`, `SegmentStart4`, `SoakStartTime`, `SegmentStart5`,
`SoakEndTime`, `DurationFinalSoak`, `MaxSoakTemp`, `MinSoakTemp`, `MaxSoakPressure`,
`MinSoakPressure`, `SegmentStart6`, `QuenchTemp`, `DewPointMax`, `StartTimestamp`, `EndTimestamp`.
### Tags written / read (`{Machine.Code}.MesReceiver.*`)
Same set as move-in with the `MoveOut` prefix, **minus `JobSequenceNumber`**:
`MoveOutReadyFlag` (gate), `MoveOutMesContainerNum` / `MoveOutOperatorName` /
`MoveOutNumberWorkOrders` / `MoveOutPartNumbers[]` / `MoveOutWorkOrderNumbers[]` (write),
`MoveOutFlag` (trigger), `MoveOutCompleteFlag` (watch), `MoveOutSuccessfulFlag` /
`MoveOutErrorText` / `MoveOutBatchID` (result).
---
## How the handshake works (MXAccess)
`MesNotifier` holds a single process-wide `LMXProxyServerClass` (handle registered as
`"MesNotifier"`, wired to `OnDataChange` + `OnWriteComplete`). For each request it:
1. **Advise**`AddItem(path)` then `AdviseSupervisory`; the first `OnDataChange` per tag
resolves that tag's read task (success = quality `192` / OPC-Good). Values are coerced to the
tag's CLR type via `Convert.ChangeType`.
2. **Write**`LMXProxyServerClass.Write`; `OnWriteComplete` resolves the write task with the
driver's success flag.
3. **Watch**`Tag.OnValue(target)` completes when a subsequent `OnDataChange` reports a value
equal to the target (used for the `…CompleteFlag → true` step).
4. **Unadvise** — every tag is unsubscribed and removed at the end of the request.
Everything is bounded by the per-request **30 s** `CancellationTokenSource`; on expiry, all
pending reads/writes/watches resolve as `false`.
The move-in/move-out contract is therefore a classic flag protocol on the machine's `MesReceiver`:
**read ready → write payload + set request flag → wait for complete flag → read success/error/batch**.
---
## Underlying data model — `Machine`
SQL table backing machine lookup (`APIServer.ServiceModel/DTO/Machine.cs`); `Code` is also the
MXAccess tag prefix and `SAPID` is the move-in/move-out selector:
| Column | Type | Constraints |
|--------|------|-------------|
| `MachineID` | int | PK, auto-increment |
| `Code` | string | Required, ≤ 50 — **MXAccess tag prefix** (`{Code}.MesReceiver.*`) |
| `Name` | string | Required, ≤ 50 |
| `ZTag` | string | ≤ 10 |
| `SAPID` | string | ≤ 10 — **move-in/out selector** |
| `Description` | string | ≤ 256 |
| `TimeZone` | string | Required, ≤ 128 |
| `MultipleBatch` / `MultipleCycle` / `Active` | bool | |
| `LastUpdate` | DateTime | |
| `LastUpdateBy` | string | Required, ≤ 128 |
| `OtherData` | string | Max-length text — **gates cycle storage** when it contains `"StoreCycleDataForMES"` |
---
## Behavior notes & gotchas
- **Work-order arrays are fixed length 50.** `ToFixedLength(50)` always writes exactly 50-element
string arrays (padded with `null`); a 51st+ work order is **silently dropped**. `PartNumbers`
and `WorkOrderNumbers` are written as two parallel arrays — index *i* of one corresponds to
index *i* of the other (positional pairing from the same `WorkOrders` list). `NumberWorkOrders`
carries the true count.
- **Connect-failure message gets clobbered.** The "connect" check and the "ready flag" check are
sequential `if`s with no early return; if the subscribe fails, `MoveInReadyFlag.Value` is its
default (`false`), so the ready-flag check also fires and **overwrites** `ErrorText` with
`"…ready flag not set to true"`. A connection problem can surface as a ready-flag error.
- **`BatchID` is null unless the machine reports a non-zero batch id.** Move-out additionally
requires cycle storage to be enabled before it sets `BatchID`.
- **Cycle data is opt-in per machine.** `Data` is empty unless `Machine.OtherData` contains
`"StoreCycleDataForMES"` *and* `MoveOutBatchID != 0`.
- **Cycle-data read bypasses the test-DB redirect.** It uses a raw `ConnectionStrings["BatchDB"]`
connection against hard-coded `BT.dbo.MachineCycle`, so a `test`-environment API key (which
redirects *OrmLite* to `TestDb`) still reads cycle data from `BatchDB`.
- **`WasSuccessful` reflects the machine, not just the transport.** Even with a clean handshake,
`WasSuccessful` is whatever the machine wrote to `…SuccessfulFlag`, and `ErrorText` is the
machine's `…ErrorText`.
- **Synchronous blocking.** `MesServices` calls `.Result` on the async methods; combined with the
single shared MXAccess proxy, throughput is effectively serialized per request thread.
- **Machine lookup uses `db.Single`** on `SAPID`; returns null when unmatched (handled), and the
first match if `SAPID` is non-unique.
- The route attributes carry placeholder Swagger text (`Summary = "POST Summary"`,
`Notes = "Notes"`); cosmetic only.
---
## Quick reference (curl)
```bash
# Move in a container + work order
curl -X POST http://<host>:<port>/mes/movein \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <api-key>" \
-d '{
"SAPID":"100012345",
"OperatorName":"chamalas",
"JobSequenceNumber":"50",
"ContainerNumber":"cont-012",
"WorkOrders":[{"WorkOrderNumber":"W111111","PartNumber":"P111111"}]
}'
# Move out the same container (returns cycle Data when the machine stores it for MES)
curl -X POST http://<host>:<port>/mes/moveout \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <api-key>" \
-d '{
"SAPID":"100012345",
"OperatorName":"chamalas",
"ContainerNumber":"cont-012",
"WorkOrders":[{"WorkOrderNumber":"W111111","PartNumber":"P111111"}]
}'
```
> Exact auth header format depends on how `ApiKeyAuthProvider` is configured for the deployment
> (bearer token vs. HTTP Basic with the key as username). Confirm against the live Swagger/Postman
> metadata for the target server.
---
## ScadaBridge equivalent (porting note)
ScadaBridge re-implements these flows as **Inbound API methods** (`POST /api/{method}`,
`X-API-Key` header — *not* the ServiceStack `Authorization`/`apikey` scheme) that route to a
site's `MesReceiver` instance script via `Route.To(<instanceCode>).Call("MesMoveIn"/"MesMoveOut", …)`:
- `IpsenMESMoveIn` / `MesMoveIn``/mes/movein`; `MesMoveOut``/mes/moveout`.
- The ready/trigger/complete **flag handshake moves into the site instance script** (Site Runtime),
rather than the central API driving MXAccess tags directly.
- Machine resolution by SAP id is a `Database.QuerySingleAsync<string>("BTDB", "SELECT … Machine WHERE SAPID=@s")`
inside the inbound script (see `docs/plans/2026-06-16-ipsen-mes-movein-design.md`).
This file documents the **legacy** WWSupport contract for reference/parity during that migration.