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

16 KiB
Raw Blame History

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: MesServicesAPIServer/APIServer.ServiceInterface/MesServices.cs
  • Business logic: MesNotifierAPIServer/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.


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:

[Authenticate]
[RequiredRole("MESAPI")]
public class MesServices : Service { ... }

The caller must be authenticated and hold the MESAPI role. Configured in AppHost.Configure:

  • API keyApiKeyAuthProvider (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).
  • LDAPLdapAuthProvider.
  • 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).
{
  "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.
{
  "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.
-- 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. AdviseAddItem(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. WriteLMXProxyServerClass.Write; OnWriteComplete resolves the write task with the driver's success flag.
  3. WatchTag.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 ifs 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)

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