LmxProxy is no longer needed. Moved the entire lmxproxy/ workspace, DCL adapter files, and related docs to deprecated/. Removed LmxProxy registration from DataConnectionFactory, project reference from DCL, protocol option from UI, and cleaned up all requirement docs.
13 KiB
Component: Protocol
Purpose
Defines the gRPC protocol specification for communication between the LmxProxy Client and Host, including the proto file definition, code-first contracts, message schemas, value type system, and quality codes. The authoritative specification is docs/lmxproxy_updates.md.
Location
src/ZB.MOM.WW.LmxProxy.Host/Grpc/Protos/scada.proto— proto file (Host, proto-generated).src/ZB.MOM.WW.LmxProxy.Client/Domain/ScadaContracts.cs— code-first contracts (Client, protobuf-net.Grpc).docs/lmxproxy_updates.md— authoritative protocol specification.docs/lmxproxy_protocol.md— legacy v1 protocol documentation (superseded).
Responsibilities
- Define the gRPC service interface (
scada.ScadaService) and all message types. - Ensure wire compatibility between the Host's proto-generated code and the Client's code-first contracts.
- Specify the VTQ data model:
TypedValuefor values,QualityCodefor quality. - Document OPC UA-aligned quality codes filtered to AVEVA System Platform usage.
1. Service Definition
Service: scada.ScadaService (gRPC package: scada)
| RPC | Request | Response | Type |
|---|---|---|---|
| Connect | ConnectRequest | ConnectResponse | Unary |
| Disconnect | DisconnectRequest | DisconnectResponse | Unary |
| GetConnectionState | GetConnectionStateRequest | GetConnectionStateResponse | Unary |
| Read | ReadRequest | ReadResponse | Unary |
| ReadBatch | ReadBatchRequest | ReadBatchResponse | Unary |
| Write | WriteRequest | WriteResponse | Unary |
| WriteBatch | WriteBatchRequest | WriteBatchResponse | Unary |
| WriteBatchAndWait | WriteBatchAndWaitRequest | WriteBatchAndWaitResponse | Unary |
| Subscribe | SubscribeRequest | stream VtqMessage | Server streaming |
| CheckApiKey | CheckApiKeyRequest | CheckApiKeyResponse | Unary |
2. Value Type System (TypedValue)
Values are transmitted in their native protobuf types via a TypedValue oneof. No string serialization or parsing heuristics are used.
TypedValue {
oneof value {
bool bool_value = 1
int32 int32_value = 2
int64 int64_value = 3
float float_value = 4
double double_value = 5
string string_value = 6
bytes bytes_value = 7
int64 datetime_value = 8 // UTC DateTime.Ticks (100ns intervals since 0001-01-01)
ArrayValue array_value = 9 // typed arrays
}
}
ArrayValue contains typed repeated fields via oneof: BoolArray, Int32Array, Int64Array, FloatArray, DoubleArray, StringArray. Each contains a repeated field of the corresponding primitive.
2.1 Null Handling
- Null is represented by an unset
oneof(no field selected inTypedValue). - A null or missing VTQ message is treated as Bad quality with null value and current UTC timestamp.
2.2 Type Mapping from Internal Tag Model
| Tag Data Type | TypedValue Field |
|---|---|
bool |
bool_value |
int32 |
int32_value |
int64 |
int64_value |
float |
float_value |
double |
double_value |
string |
string_value |
byte[] |
bytes_value |
DateTime |
datetime_value (UTC Ticks as int64) |
float[] |
array_value.float_values |
int32[] |
array_value.int32_values |
| Other arrays | Corresponding ArrayValue field |
3. Quality System (QualityCode)
Quality is a structured message with an OPC UA-compatible numeric status code and a human-readable symbolic name:
QualityCode {
uint32 status_code = 1 // OPC UA-compatible numeric status code
string symbolic_name = 2 // Human-readable name (e.g., "Good", "BadSensorFailure")
}
3.1 Category Extraction
Category derived from high bits via (statusCode & 0xC0000000):
0x00000000= Good0x40000000= Uncertain0x80000000= Bad
public static bool IsGood(uint statusCode) => (statusCode & 0xC0000000) == 0x00000000;
public static bool IsBad(uint statusCode) => (statusCode & 0xC0000000) == 0x80000000;
3.2 Supported Quality Codes
Filtered to codes actively used by AVEVA System Platform, InTouch, and OI Server/DAServer (per AVEVA Tech Note TN1305):
Good Quality:
| Symbolic Name | OPC UA Status Code | AVEVA OPC DA Hex | Description |
|---|---|---|---|
Good |
0x00000000 |
0x00C0 |
Value is reliable, non-specific |
GoodLocalOverride |
0x00D80000 |
0x00D8 |
Manually overridden; input disconnected |
Uncertain Quality:
| Symbolic Name | OPC UA Status Code | AVEVA OPC DA Hex | Description |
|---|---|---|---|
UncertainLastUsableValue |
0x40900000 |
0x0044 |
External source stopped writing; value is stale |
UncertainSensorNotAccurate |
0x42390000 |
0x0050 |
Sensor out of calibration or clamped |
UncertainEngineeringUnitsExceeded |
0x40540000 |
0x0054 |
Outside defined engineering limits |
UncertainSubNormal |
0x40580000 |
0x0058 |
Derived from insufficient good sources |
Bad Quality:
| Symbolic Name | OPC UA Status Code | AVEVA OPC DA Hex | Description |
|---|---|---|---|
Bad |
0x80000000 |
0x0000 |
Non-specific bad; value not useful |
BadConfigurationError |
0x80040000 |
0x0004 |
Server config problem (e.g., item deleted) |
BadNotConnected |
0x808A0000 |
0x0008 |
Input not logically connected to source |
BadDeviceFailure |
0x806B0000 |
0x000C |
Device failure detected |
BadSensorFailure |
0x806D0000 |
0x0010 |
Sensor failure detected |
BadLastKnownValue |
0x80050000 |
0x0014 |
Comm failed; last known value available |
BadCommunicationFailure |
0x80050000 |
0x0018 |
Comm failed; no last known value |
BadOutOfService |
0x808F0000 |
0x001C |
Block off-scan/locked; item inactive |
BadWaitingForInitialData |
0x80320000 |
— | Initializing; OI Server establishing communication |
Notes:
- AVEVA OPC DA quality codes use a 16-bit structure: 2 bits major (Good/Bad/Uncertain), 4 bits minor (sub-status), 2 bits limit (Not Limited, Low, High, Constant). The OPC UA status codes above are the standard UA equivalents.
- The limit bits are appended to any quality code. For example,
Good + High Limited=0x00C2in OPC DA. In OPC UA, limits are conveyed via separate status code bits but the base code remains the same.
3.3 Error Condition Mapping
| Scenario | Quality |
|---|---|
| Normal read | Good (0x00000000) |
| Tag not found | BadConfigurationError (0x80040000) |
| Tag read exception / comms loss | BadCommunicationFailure (0x80050000) |
| Sensor failure | BadSensorFailure (0x806D0000) |
| Device failure | BadDeviceFailure (0x806B0000) |
| Stale value | UncertainLastUsableValue (0x40900000) |
| Block off-scan / disabled | BadOutOfService (0x808F0000) |
| Local override active | GoodLocalOverride (0x00D80000) |
| Initializing / waiting for first value | BadWaitingForInitialData (0x80320000) |
| Write to read-only tag | WriteResult.success=false, message indicates read-only |
| Type mismatch on write | WriteResult.success=false, message indicates type mismatch |
4. Message Schemas
4.1 VtqMessage
The core data type for tag value transport:
| Field | Proto Type | Order | Description |
|---|---|---|---|
| tag | string | 1 | Tag address |
| value | TypedValue | 2 | Typed value (native protobuf types) |
| timestamp_utc_ticks | int64 | 3 | UTC DateTime.Ticks (100ns intervals since 0001-01-01) |
| quality | QualityCode | 4 | Structured quality with status code and symbolic name |
A null or missing VTQ message is treated as Bad quality with null value and current UTC timestamp.
4.2 Connection Messages
ConnectRequest: client_id (string), api_key (string)
ConnectResponse: success (bool), message (string), session_id (string — 32-char hex GUID)
DisconnectRequest: session_id (string)
DisconnectResponse: success (bool), message (string)
GetConnectionStateRequest: session_id (string)
GetConnectionStateResponse: is_connected (bool), client_id (string), connected_since_utc_ticks (int64)
4.3 Read Messages
ReadRequest: session_id (string), tag (string)
ReadResponse: success (bool), message (string), vtq (VtqMessage)
ReadBatchRequest: session_id (string), tags (repeated string)
ReadBatchResponse: success (bool), message (string), vtqs (repeated VtqMessage)
4.4 Write Messages
WriteRequest: session_id (string), tag (string), value (TypedValue)
WriteResponse: success (bool), message (string)
WriteItem: tag (string), value (TypedValue)
WriteResult: tag (string), success (bool), message (string)
WriteBatchRequest: session_id (string), items (repeated WriteItem)
WriteBatchResponse: success (bool), message (string), results (repeated WriteResult)
4.5 WriteBatchAndWait Messages
WriteBatchAndWaitRequest:
session_id(string)items(repeated WriteItem) — values to writeflag_tag(string) — tag to poll after writesflag_value(TypedValue) — expected value (type-aware comparison)timeout_ms(int32) — max wait time (default 5000ms if ≤ 0)poll_interval_ms(int32) — polling interval (default 100ms if ≤ 0)
WriteBatchAndWaitResponse:
success(bool)message(string)write_results(repeated WriteResult)flag_reached(bool) — whether the flag value was matchedelapsed_ms(int32) — total elapsed time
Behavior:
- All writes execute first. If any write fails, returns immediately with
success=false. - If writes succeed, polls
flag_tagatpoll_interval_msintervals. - Uses type-aware
TypedValueEquals()comparison (see Section 4.5.1). - If flag matches before timeout:
success=true,flag_reached=true. - If timeout expires:
success=true,flag_reached=false(timeout is not an error).
4.5.1 Flag Comparison Rules
Type-aware comparison via TypedValueEquals():
- Both values must have the same
oneofcase (same type). Mismatched types are never equal. - Numeric comparison uses the native type's equality (no floating-point string round-trip issues).
- String comparison is case-sensitive.
- Bool comparison is direct equality.
- Null (unset
oneof) equals null. Null does not equal any set value. - Array comparison: element-by-element equality, same length required.
datetime_valuecompared asint64equality (tick-level precision).
4.6 Subscription Messages
SubscribeRequest: session_id (string), tags (repeated string), sampling_ms (int32)
Response: streamed VtqMessage items.
4.7 API Key Messages
CheckApiKeyRequest: api_key (string)
CheckApiKeyResponse: is_valid (bool), message (string)
5. Dual gRPC Stack Compatibility
The Host and Client use different gRPC implementations:
| Aspect | Host | Client |
|---|---|---|
| Stack | Grpc.Core (C-core) | Grpc.Net.Client |
| Contract | Proto file (scada.proto) + Grpc.Tools codegen |
Code-first ([ServiceContract], [DataContract]) via protobuf-net.Grpc |
| Runtime | .NET Framework 4.8 | .NET 10 |
Both target scada.ScadaService and produce identical wire format. Field ordering in [DataMember(Order = N)] matches proto field numbers.
6. V1 Legacy Protocol
The current codebase implements the v1 protocol. The following describes v1 behavior that will be replaced during migration to v2.
6.1 V1 Value Encoding
All values transmitted as strings:
- Write direction: server parses string values in order: bool → int → long → double → DateTime → raw string.
- Read direction: server serializes via
.ToString()(bool → lowercase, DateTime → ISO-8601, arrays → JSON). - Client parses: double → bool → null (empty string) → raw string.
6.2 V1 Quality
Three-state string quality ("Good", "Uncertain", "Bad", case-insensitive). OPC UA numeric ranges: ≥192 = Good, 64–191 = Uncertain, <64 = Bad.
6.3 V1 → V2 Field Changes
| Message | Field | V1 Type | V2 Type |
|---|---|---|---|
| VtqMessage | value | string | TypedValue |
| VtqMessage | quality | string | QualityCode |
| WriteRequest | value | string | TypedValue |
| WriteItem | value | string | TypedValue |
| WriteBatchAndWaitRequest | flag_value | string | TypedValue |
All RPC signatures remain unchanged. Only value and quality fields change type.
6.4 Migration Strategy
Clean break — no backward compatibility layer. All clients and servers updated simultaneously. This is appropriate because LmxProxy is an internal protocol with a small, controlled client count. Dual-format support adds complexity with no long-term benefit.
Dependencies
- Grpc.Core + Grpc.Tools — proto compilation and server hosting (Host).
- protobuf-net.Grpc — code-first contracts (Client).
- Grpc.Net.Client — HTTP/2 transport (Client).
Interactions
- GrpcServer implements the service defined by this protocol.
- Client consumes the service defined by this protocol.
- MxAccessClient is the backend that executes the operations requested via the protocol.