# 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

```csharp
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, 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.
