Pattern 3: Connection Management¶
Start here: For overall communication architecture, see Communication Overview.
TCP devices: If your device communicates over TCP request/reply, prefer the framework TCP transport instead of raw
TcpClient. See TCP Transport and TCP Request-Reply. Custom transport code is for native SDK, gateway, streaming, or protocol-specific lifecycle needs.
Primary Pattern¶
Implement IDeviceConnection on the root Thing. Connect returns true only after the device is authenticated, initialized, and ready for commands/events. Disconnect tears down protocol resources. The framework owns lifecycle state, retry, debouncing, metrics, and connection status publication.
public partial class Controller(Transport transport, ILogger<Controller> logger)
: IDynamicProtocolAddressable<int>, IDeviceConnection, IConnectionAware
{
public DynamicAddress<int> Address { get; internal set; }
public async Task<bool> Connect(ConnectionContext context, CancellationToken ct)
{
logger.LogInformation(
"Connecting to controller (attempt={Attempt}, reconnect={Reconnect})",
context.AttemptNumber,
context.IsReconnect);
Address = await transport.ConnectController(this, ct);
if (!Address.HasValue)
return false;
Protocol.ProtocolId = Address.Value;
if (!await Protocol.Initialize(ct))
{
await transport.DisconnectController(Address.Value, ct);
Address = DynamicAddress<int>.None;
Protocol.ProtocolId = null;
return false;
}
return true;
}
public async Task Disconnect(CancellationToken ct)
{
if (!Address.HasValue)
return;
Protocol.ProtocolId = null;
await transport.DisconnectController(Address.Value, ct);
Address = DynamicAddress<int>.None;
}
public Task OnConnected(ConnectionContext context)
{
logger.LogInformation("Controller connected");
return Task.CompletedTask;
}
public Task OnDisconnected(ConnectionContext context)
{
logger.LogInformation("Controller disconnected");
return Task.CompletedTask;
}
}
Key Points¶
- Return
falsefromConnect()to trigger framework retry. - Return
trueonly after authentication, initialization, address binding, and event/polling readiness are complete. - Do not call raw
SignalConnected()orSignalDisconnected()in normalIDeviceConnectionimplementations; that is legacy/custom-only lifecycle plumbing. - Run background receive loops, alert streams, SDK event streams, or gateway monitors from services that cleanly stop on disconnect.
- Keep long-lived services ID/address based; do not retain direct Thing references across async callbacks or timers.
- Implement
IConnectionAwarewhen the adapter needs post-connect or post-disconnect cleanup such as stream registry clearing.
Connection Lifecycle¶
stateDiagram-v2
[*] --> Offline
Offline --> Connecting: framework calls Connect()
Connecting --> Online: Connect() returns true
Connecting --> Offline: Connect() returns false or throws
Online --> Offline: Disconnect(), stream failure, watchdog, or transport loss
Offline --> Connecting: framework retry
Custom Transport Responsibilities¶
For native SDK, gateway, HTTP stream, or other custom transports, document and implement:
- external process/native library startup and shutdown
- authentication and initialization sequence
- event stream/message pump lifecycle
- heartbeat/watchdog behavior
- reconnect cleanup and stale resource cleanup
- address binding between protocol IDs and Things
- event replay or event loss semantics
See Also¶
- Pattern 10: Error Handling - retry strategies
- Pattern 12: Status Polling - polling respects connection lifecycle