Communication Layer Overview¶
This document explains how adapters communicate with physical devices. Read this first, then dive into specific topics.
Architecture¶
graph TB
subgraph Adapter["Adapter Process"]
subgraph Transport["Transport (Shared Resource)"]
TCP["TCP Transport"]
UDP["UDP Transport"]
SDK["Custom Transport (Vendor SDK)"]
end
subgraph Connections["Connections (per device)"]
C1["Connection 1"]
C2["Connection 2"]
C3["Connection N"]
end
subgraph Protocol["Protocol Layer"]
P1["Protocol 1"]
P2["Protocol 2"]
P3["Protocol N"]
end
subgraph Things["Thing Layer"]
T1["Panel 1"]
T2["Panel 2"]
T3["Panel N"]
end
end
subgraph Devices["Physical Devices"]
D1["Device 1"]
D2["Device 2"]
D3["Device N"]
end
Transport --> C1 & C2 & C3
C1 --> P1 --> T1
C2 --> P2 --> T2
C3 --> P3 --> T3
C1 <--> D1
C2 <--> D2
C3 <--> D3
Conceptual Model: TCP Stack Analogy¶
Think of it like the operating system's network stack:
| OS Network Stack | Adapter Framework | Responsibility |
|---|---|---|
| Network interface | Transport | Single shared resource, manages raw I/O |
| TCP/IP stack | Transport | Multiplexing, buffering, backpressure |
| Socket | Connection | Per-device channel, ordered delivery |
| Application | Protocol + Thing | Business logic, command/event handling |
Key insight: Transport is shared (one per adapter), Connections are per-device (like sockets).
Transport vs Connection¶
graph LR
subgraph Transport["Transport (singleton)"]
direction TB
T["Shared I/O resource"]
B["Buffer management"]
M["Multiplexing"]
end
subgraph C1["Connection 1"]
Q1["Command Queue"]
F1["Frame Parser"]
end
subgraph C2["Connection 2"]
Q2["Command Queue"]
F2["Frame Parser"]
end
Transport --> C1 & C2
C1 --> D1["Device 1"]
C2 --> D2["Device 2"]
| Aspect | Transport | Connection |
|---|---|---|
| Lifetime | Adapter lifetime | Device session |
| Cardinality | One per adapter | One per device |
| Packets | May interleave between devices | Ordered within device |
| Responsibility | Raw I/O, buffering | Framing, command sequencing |
Message Flow Guarantees¶
Within one Connection:
- Packets delivered in order
- No interleaving (atomic delivery)
- Command queue serializes outbound
Across Connections (in Transport):
- Packets may interleave
- No ordering between devices
- Fair scheduling (round-robin or priority)
When to Use What¶
flowchart TD
Start["Device communication"] --> Q1{"Communication method?"}
Q1 -->|"Raw TCP/UDP"| TCP["transport: tcp"]
Q1 -->|"Vendor SDK with callbacks"| Custom["transport: custom"]
Q1 -->|"REST API"| HTTP["HttpClient (no transport)"]
TCP --> Frame["Implement IFrameParser"]
Custom --> SDK["Implement ISharedResourceProvider"]
HTTP --> Direct["Direct HTTP calls in Protocol"]
Frame --> Protocol["Protocol handles frames"]
SDK --> Protocol
Direct --> Protocol
| Device Type | Transport | You Implement |
|---|---|---|
| TCP with custom protocol | tcp |
IFrameParser, Protocol partial |
| UDP with custom protocol | udp |
IFrameParser, Protocol partial |
| Vendor SDK (callbacks) | custom |
ISharedResourceProvider, Transport partial |
| REST/HTTP API | none | Protocol with HttpClient |
| gRPC | none | Protocol with gRPC client |
Data Flow¶
Outbound (Command → Device)¶
sequenceDiagram
participant Thing
participant Protocol
participant Connection
participant Transport
participant Device
Thing->>Protocol: Command request
Protocol->>Protocol: Build packet
Protocol->>Connection: Enqueue packet
Connection->>Connection: Command queue (serialize)
Connection->>Transport: Write bytes
Transport->>Device: Send over network
Device-->>Transport: ACK
Transport-->>Connection: ACK received
Connection-->>Protocol: Command complete
Protocol-->>Thing: Result
Inbound (Device → Event)¶
sequenceDiagram
participant Device
participant Transport
participant Connection
participant Protocol
participant EventRouter
participant Thing
Device->>Transport: Raw bytes
Transport->>Connection: Buffered data
Connection->>Connection: Frame parser
Connection->>Protocol: Parsed frame
Protocol->>Protocol: Convert to domain event
Protocol->>EventRouter: Dispatch event
EventRouter->>Thing: Matched handler
Thing->>Thing: Update status, publish PQ event
Command Execution Pattern¶
Two patterns for sending commands and handling responses:
Immediate Command (ACK only)¶
Device acknowledges command immediately, no follow-up event needed.
sequenceDiagram
participant UI
participant Thing
participant Protocol
participant Device
UI->>Thing: Unlock command
Thing->>Thing: Set pending status
Thing->>Protocol: Send unlock
Protocol->>Device: Unlock packet
Device-->>Protocol: ACK
Protocol-->>Thing: Success
Thing->>Thing: Set final status
Thing-->>UI: Command succeeded
Use ExecuteCommand without protocol parameter:
- Set pending status
- Send command, wait for ACK
- On ACK: set final status, return Succeeded
- On failure: revert status, return Failed
Event-Confirmed Command¶
Device acknowledges command, then sends event when action completes.
sequenceDiagram
participant UI
participant Thing
participant Protocol
participant Device
UI->>Thing: Unlock command
Thing->>Thing: Set pending status
Thing->>Protocol: Register event intercept
Thing->>Protocol: Send unlock
Protocol->>Device: Unlock packet
Device-->>Protocol: ACK
Protocol-->>Thing: ACK received
Thing-->>UI: Command succeeded (optimistic)
Note over Device: Door unlocks physically
Device->>Protocol: Door unlocked event
Protocol->>Thing: Intercepted event
Thing->>Thing: Set confirmed status
Use ExecuteCommand with protocol and predicate:
- Set pending status
- Register event intercept (before send to avoid race)
- Send command, wait for ACK
- On ACK: return Succeeded immediately (command accepted by device/control system)
- Background: wait for audited execution event
- On event: set final status via
onSuccess - On timeout: revert status, call
onFault
Do not confuse command acceptance with final execution. ExecuteCommand intentionally returns the command result after ACK so delayed actions can be accepted while the Thing remains pending until the later execution event arrives.
Use audited device events/log entries for command pairing. Status-change callbacks and poll snapshots are not equivalent to audited execution events unless the protocol has no audited event for that action and the adapter documents the fallback.
Protocol Layer Responsibilities¶
graph TB
subgraph Protocol["Protocol Layer (your code)"]
Parse["Parse device frames"]
Build["Build command packets"]
Translate["Translate to/from domain events"]
Queue["Command queue management"]
end
subgraph Framework["Framework Provides"]
Transport["Transport (TCP/UDP)"]
Router["EventRouter"]
Intercept["Event interception"]
Status["Status management"]
end
Parse --> Router
Build --> Transport
Translate --> Router
Queue --> Transport
Framework --> Protocol
Protocol is the bridge between framework abstractions and device-specific wire format.
Connection Lifecycle¶
stateDiagram-v2
[*] --> Disconnected
Disconnected --> Connecting: Connect()
Connecting --> Connected: Success
Connecting --> Disconnected: Failure (retry with backoff)
Connected --> Disconnected: Network error
Connected --> Disconnected: Disconnect()
Connected --> Connected: Normal operation
Framework handles:
- Automatic retry with exponential backoff
- Connection metrics (MTBF, MTTR, availability)
- Debouncing rapid connect/disconnect cycles
Related Documentation¶
- TCP Transport - Framework TCP transport details
- Protocol Layer - Protocol implementation patterns
- Connection Management - IDeviceConnection lifecycle
- Shared Resources - Custom transport implementation