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
This commit is contained in:
Joseph Doherty
2026-06-26 04:13:19 -04:00
parent 33da8c797c
commit 8a78e759c0
6 changed files with 1009 additions and 0 deletions
+266
View File
@@ -0,0 +1,266 @@
# WWSupport MES API — Alarm Status API
Reference for the alarm-status endpoints exposed by the **WWSupport / APIServer** ServiceStack
service. The API reads live machine-alarm state out of AVEVA Wonderware (System Platform /
Galaxy) via MXAccess and returns it to MES callers.
- **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. `"AckDT": null`).
---
## Endpoints
| Verb | Route | Request DTO | Response DTO |
|------|-------|-------------|--------------|
| `POST` | `/mes/alarmstatus` | `AlarmStatusRequest` | `AlarmStatusResponse` |
| `POST` | `/mes/simplealarmstatus` | `SimpleAlarmStatusRequest` | `AlarmStatusResponse` |
Both endpoints return the **same** `AlarmStatusResponse` shape. Both are dispatched through
ServiceStack `Any(...)` handlers in `MesServices`, which resolve the singleton `MesNotifier` and
call `AlarmStatus(...)` / `SimpleAlarmStatus(...)`.
The service also enables `PostmanFeature` and `OpenApiFeature` (Swagger), so a running instance
exposes a browsable contract and a Postman collection.
---
## Authentication & authorization
All MES services are decorated with:
```csharp
[Authenticate]
[RequiredRole("MESAPI")]
public class MesServices : Service { ... }
```
The caller must be authenticated **and** hold the `MESAPI` role. Auth is configured in
`AppHost.Configure`:
- **API key** — `ApiKeyAuthProvider`
- `SessionCacheDuration = 30 minutes`
- `RequireSecureConnection = false` (HTTP is accepted; TLS not enforced)
- An API key whose `Environment == "test"` is routed to the `TestDb` connection instead of the
production DB (`AppHost.GetDbConnection`).
- **LDAP** — `LdapAuthProvider` (directory credentials).
- `AllowGetAuthenticateRequests = true`.
User/role data is persisted with `OrmLiteAuthRepository` (`UseDistinctRoleTables = true`) in the
same SQL Server database.
---
## `POST /mes/simplealarmstatus`
The convenience endpoint: identify a machine by SAPID, get back its MES-relevant alarms.
### Request — `SimpleAlarmStatusRequest`
| Field | Type | Notes |
|-------|------|-------|
| `SAPID` | string | SAP identifier of the machine. Required. |
```json
{ "SAPID": "100012345" }
```
### Behavior
1. Look up the `Machine` by `SAPID`. If not found → `WasSuccessful = false`,
`ErrorText = "Failed to find machine with SAPID '<id>'"`.
2. Select that machine's alarms **filtered to `FlaggedForMES = true` only**.
3. Read live tag state and return every alarm that is **currently triggered** (`InAlarm == true`).
Notes specific to this endpoint:
- Always **flagged-only** (cannot return non-MES alarms).
- Does **not** honor an "include acked" toggle — acked alarms are always included (with
`StatusCode = "Triggered.Acked"`).
---
## `POST /mes/alarmstatus`
The full endpoint: pick the machine by any one of several keys, and filter the alarm set.
### Request — `AlarmStatusRequest`
| Field | Type | Notes |
|-------|------|-------|
| `MachineFilter` | `MachineFilter` | Selects the machine (see below). Defaults to empty. |
| `AlarmFilter` | `AlarmFilter` | Filters the alarms (see below). Defaults to "all flagged + unflagged, triggered + acked". |
#### `MachineFilter`
| Field | Type | Notes |
|-------|------|-------|
| `MachineID` | int? | DB primary key. |
| `SAPID` | string | SAP identifier. |
| `ZTag` | string | Z-tag identifier. |
| `Code` | string | Machine code (also the MXAccess tag prefix). |
**Resolution precedence** (first non-empty wins): `SAPID``Code``ZTag``MachineID`.
At least one selector must be supplied; if all are empty/unmatched the call fails with
`"Failed to find machine with given machine filter '<dump>'"`. A supplied-but-unmatched selector
fails with a selector-specific message, e.g. `"Failed to find machine with Code '<code>'"`.
#### `AlarmFilter`
| Field | Type | Default | Effect |
|-------|------|---------|--------|
| `NameFilter` | string | `null` | Case-insensitive **substring** match on alarm `Name`. |
| `MinSeverity` | int? | `null` | Keep alarms with `Severity >= MinSeverity`. |
| `MaxSeverity` | int? | `null` | Keep alarms with `Severity <= MaxSeverity`. |
| `IncludeTriggered` | bool | `true` | **Currently unused** — see Behavior notes. |
| `IncludeAcked` | bool | `true` | When `false`, acked alarms are excluded from the result. |
| `FlaggedOnly` | bool | `false` | When `true`, restrict to `FlaggedForMES = true` alarms. |
```json
{
"MachineFilter": { "SAPID": "100012345" },
"AlarmFilter": {
"MinSeverity": 500,
"FlaggedOnly": true,
"IncludeAcked": false
}
}
```
### Behavior
1. Resolve the machine via `MachineFilter` precedence (above).
2. Load all `MachineAlarm` rows for that machine, then apply the in-process `AlarmFilter` in this
order: `FlaggedOnly``MinSeverity``MaxSeverity``NameFilter`.
3. Read live tag state; return every remaining alarm that is **triggered** (`InAlarm == true`),
skipping acked alarms when `IncludeAcked == false`.
---
## Response — `AlarmStatusResponse`
| Field | Type | Notes |
|-------|------|-------|
| `WasSuccessful` | bool | `false` on any lookup or tag-read failure. |
| `ErrorText` | string | Populated when `WasSuccessful == false`. |
| `Alarms` | `AlarmInfo[]` | Triggered alarms matching the request. **Cleared if `WasSuccessful == false`.** |
### `AlarmInfo`
| Field | Type | Source |
|-------|------|--------|
| `Name` | string | `MachineAlarm.Name`. |
| `HierarchicalName` | string | `"{Machine.Code}.{Name}"`. |
| `Description` | string | Live `…​.DescAttrName` tag value. |
| `IsFlaggedForMES` | bool | `MachineAlarm.FlaggedForMES`. |
| `Severity` | int | `MachineAlarm.Severity` (0999). |
| `StatusCode` | string | `"Triggered"`, or `"Triggered.Acked"` when acked. |
| `TriggeredDT` | DateTime | Live `…​.TimeAlarmOn` tag value. |
| `AckDT` | DateTime? | Live `…​.TimeAlarmAcked` tag value (null if unacked). |
| `AckComment` | string | Live `…​.AckMsg` tag value. |
### Example response
```json
{
"WasSuccessful": true,
"ErrorText": null,
"Alarms": [
{
"Name": "HighVacuumFault",
"HierarchicalName": "Z28061.HighVacuumFault",
"Description": "High vacuum sensor out of range",
"IsFlaggedForMES": true,
"Severity": 800,
"StatusCode": "Triggered.Acked",
"TriggeredDT": "2026-06-25T01:14:22",
"AckDT": "2026-06-25T01:16:05",
"AckComment": "Investigating - day shift"
}
]
}
```
---
## How alarm state is read (MXAccess)
Alarm configuration (name, severity, MES flag) lives in SQL; **live state** is read from
Wonderware at request time:
- For each `MachineAlarm`, an `AlarmTagset` (`AlarmTagset.cs`) builds MXAccess tag paths of the
form `{Machine.Code}.{Alarm.Name}.<suffix>`, where `<suffix>` is one of:
`Quality`, `InAlarm`, `TimeAlarmOn`, `DescAttrName`, `Acked`, `TimeAlarmAcked`, `AckMsg`.
- `MesNotifier` subscribes via `LMXProxyServerClass.AdviseSupervisory` (handle registered as
`"MesNotifier"`), waits for the first data change per tag, then unsubscribes.
- **Quality gate:** every alarm's `Quality` must be OPC-Good (`192`); otherwise the whole request
fails with `ErrorText = "Failed to read machine alarm status"` and `Alarms` is cleared.
- Detail tags (`TimeAlarmOn`, `DescAttrName`, `Acked`, `TimeAlarmAcked`, `AckMsg`) are only read
for alarms whose `InAlarm` is true.
- **Per-request timeout:** 30 seconds (`CancellationTokenSource(30000)`); on timeout the pending
reads resolve as failed.
---
## Underlying data model — `MachineAlarm`
SQL table backing alarm configuration (`APIServer.ServiceModel/DTO/MachineAlarm.cs`):
| Column | Type | Constraints |
|--------|------|-------------|
| `MachineAlarmID` | int | PK, auto-increment |
| `MachineID` | int | FK → `Machine` |
| `Machine` | `Machine` | OrmLite `[Reference]` |
| `Name` | string | Required, ≤ 256 |
| `Severity` | int | 0999 |
| `FlaggedForMES` | bool | Marks an alarm as MES-relevant |
| `LastUpdate` | DateTime | |
| `LastUpdateBy` | string | Required, ≤ 128 |
| `OtherData` | string | Max-length text |
---
## Behavior notes & gotchas
- **`AlarmFilter.IncludeTriggered` is declared but never used.** Both endpoints only ever return
alarms whose `InAlarm` tag is `true`; setting `IncludeTriggered = false` has no effect.
- **Only triggered alarms are returned.** There is no "all configured alarms" mode — an alarm not
currently in alarm state never appears, regardless of filters.
- **`/simplealarmstatus` is flagged-only and ignores ack filtering**; use `/mes/alarmstatus` with
`FlaggedOnly` / `IncludeAcked` for control over those.
- **`MachineFilter` precedence matters** — supplying both `SAPID` and `Code` uses `SAPID`; the
others are ignored.
- **All-or-nothing on read errors.** A single bad-quality tag fails the whole response (success
flag flips, alarm list is emptied) rather than returning a partial set.
- **Severity bounds are inclusive** on both `MinSeverity` and `MaxSeverity`.
- The route attributes carry placeholder Swagger text (`Summary = "POST Summary"`,
`Notes = "Notes"`); these are cosmetic.
---
## Quick reference (curl)
```bash
# Simple — flagged alarms for one machine by SAPID
curl -X POST http://<host>:<port>/mes/simplealarmstatus \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <api-key>" \
-d '{"SAPID":"100012345"}'
# Full — filtered alarms, machine chosen by Code
curl -X POST http://<host>:<port>/mes/alarmstatus \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <api-key>" \
-d '{
"MachineFilter": { "Code": "Z28061" },
"AlarmFilter": { "MinSeverity": 500, "IncludeAcked": false }
}'
```
> 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.