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.
This commit is contained in:
301
deprecated/lmxproxy/docs/requirements/Component-Protocol.md
Normal file
301
deprecated/lmxproxy/docs/requirements/Component-Protocol.md
Normal file
@@ -0,0 +1,301 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user