Files
scadalink-design/deprecated/lmxproxy/docs/requirements/Component-Protocol.md
Joseph Doherty 9dccf8e72f deprecate(lmxproxy): move all LmxProxy code, tests, and docs to deprecated/
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.
2026-04-08 15:56:23 -04:00

13 KiB
Raw Blame History

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: TypedValue for values, QualityCode for 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 in TypedValue).
  • 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 = Good
  • 0x40000000 = Uncertain
  • 0x80000000 = 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 = 0x00C2 in 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 write
  • flag_tag (string) — tag to poll after writes
  • flag_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 matched
  • elapsed_ms (int32) — total elapsed time

Behavior:

  1. All writes execute first. If any write fails, returns immediately with success=false.
  2. If writes succeed, polls flag_tag at poll_interval_ms intervals.
  3. Uses type-aware TypedValueEquals() comparison (see Section 4.5.1).
  4. If flag matches before timeout: success=true, flag_reached=true.
  5. 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 oneof case (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_value compared as int64 equality (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, 64191 = 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.