- 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
10 KiB
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:
[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 —
ApiKeyAuthProviderSessionCacheDuration = 30 minutesRequireSecureConnection = false(HTTP is accepted; TLS not enforced)- An API key whose
Environment == "test"is routed to theTestDbconnection 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. |
{ "SAPID": "100012345" }
Behavior
- Look up the
MachinebySAPID. If not found →WasSuccessful = false,ErrorText = "Failed to find machine with SAPID '<id>'". - Select that machine's alarms filtered to
FlaggedForMES = trueonly. - 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. |
{
"MachineFilter": { "SAPID": "100012345" },
"AlarmFilter": {
"MinSeverity": 500,
"FlaggedOnly": true,
"IncludeAcked": false
}
}
Behavior
- Resolve the machine via
MachineFilterprecedence (above). - Load all
MachineAlarmrows for that machine, then apply the in-processAlarmFilterin this order:FlaggedOnly→MinSeverity→MaxSeverity→NameFilter. - Read live tag state; return every remaining alarm that is triggered (
InAlarm == true), skipping acked alarms whenIncludeAcked == 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 (0–999). |
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
{
"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, anAlarmTagset(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. MesNotifiersubscribes viaLMXProxyServerClass.AdviseSupervisory(handle registered as"MesNotifier"), waits for the first data change per tag, then unsubscribes.- Quality gate: every alarm's
Qualitymust be OPC-Good (192); otherwise the whole request fails withErrorText = "Failed to read machine alarm status"andAlarmsis cleared. - Detail tags (
TimeAlarmOn,DescAttrName,Acked,TimeAlarmAcked,AckMsg) are only read for alarms whoseInAlarmis 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 | 0–999 |
FlaggedForMES |
bool | Marks an alarm as MES-relevant |
LastUpdate |
DateTime | |
LastUpdateBy |
string | Required, ≤ 128 |
OtherData |
string | Max-length text |
Behavior notes & gotchas
AlarmFilter.IncludeTriggeredis declared but never used. Both endpoints only ever return alarms whoseInAlarmtag istrue; settingIncludeTriggered = falsehas 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.
/simplealarmstatusis flagged-only and ignores ack filtering; use/mes/alarmstatuswithFlaggedOnly/IncludeAckedfor control over those.MachineFilterprecedence matters — supplying bothSAPIDandCodeusesSAPID; 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
MinSeverityandMaxSeverity. - The route attributes carry placeholder Swagger text (
Summary = "POST Summary",Notes = "Notes"); these are cosmetic.
Quick reference (curl)
# 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
ApiKeyAuthProvideris 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.