Skip to content

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