Bridge Discovery API
mDNS/DNS-SD 기반으로 네트워크의 Bridge.Host를 자동 검색하고 Engine에 바인딩하는 REST API입니다.
기본 정보
| 항목 | 값 |
|---|---|
| Base URL (Engine) | /api/discovery |
| Base URL (Bridge) | /api/bridge |
| 인증 | JWT 토큰 ([Authorize] 필수, Engine 측) |
| Content-Type | application/json |
Engine 측 엔드포인트
Engine/Admin에서 호출하여 Bridge를 검색·관리합니다.
엔드포인트 요약
| 메서드 | 경로 | 설명 |
|---|---|---|
| GET | /api/discovery/scan | mDNS 네트워크 스캔 |
| POST | /api/discovery/bind | Bridge 바인딩 |
| DELETE | /api/discovery/unbind/{bridgeId} | Bridge 언바인딩 |
| GET | /api/discovery/bindings | 바인딩 목록 조회 |
GET /api/discovery/scan
네트워크의 Bridge를 mDNS로 스캔합니다.
쿼리 매개변수:
| 매개변수 | 타입 | 기본값 | 설명 |
|---|---|---|---|
timeoutSeconds | int | 5 | 스캔 시간 (1~30초) |
excludeBound | bool | true | 바인딩된 Bridge 제외 |
응답 (200 OK):
[
{
"bridgeId": "bridge-001",
"hostName": "caffeine-bridge.local",
"ipAddress": "192.168.1.100",
"port": 5100,
"version": "3.0.0",
"status": "Free",
"driverType": "ModbusTcp",
"tenantId": null,
"boundEngineId": null,
"tagCount": 24,
"discoveredAt": "2026-03-22T10:30:00Z",
"baseUrl": "http://192.168.1.100:5100"
}
]
POST /api/discovery/bind
Bridge를 Engine에 바인딩합니다.
요청 바디:
{
"bridgeId": "bridge-001",
"bridgeUrl": "http://192.168.1.100:5100"
}
응답 (200 OK):
{
"binding": {
"bridgeId": "bridge-001",
"ipAddress": "192.168.1.100",
"port": 5100,
"driverType": "ModbusTcp",
"bindingToken": "abc123...",
"boundAt": "2026-03-22T10:31:00Z",
"isOnline": true
},
"manifest": {
"bridgeId": "bridge-001",
"driverType": "ModbusTcp",
"driverId": "modbus-line1",
"tags": [],
"availableSubmodels": ["OperationalData", "Nameplate", "TechnicalData"],
"bindingToken": "abc123..."
}
}
오류 응답:
| 코드 | 설명 |
|---|---|
400 | bridgeId 또는 bridgeUrl 누락 |
409 | 이미 바인딩된 Bridge |
502 | Bridge 연결 실패 |
DELETE /api/discovery/unbind/{bridgeId}
Bridge 바인딩을 해제합니다.
경로 매개변수: bridgeId — 언바인딩할 Bridge 식별자
응답: 204 No Content
오류 응답: 404 — 바인딩되지 않은 Bridge
GET /api/discovery/bindings
현재 바인딩된 모든 Bridge 목록을 조회합니다.
응답 (200 OK):
[
{
"bridgeId": "bridge-001",
"ipAddress": "192.168.1.100",
"port": 5100,
"driverType": "ModbusTcp",
"bindingToken": "abc123...",
"boundAt": "2026-03-22T10:31:00Z",
"lastHeartbeatAt": "2026-03-22T10:35:00Z",
"isOnline": true,
"activeSubmodels": "[\"OperationalData\",\"Nameplate\"]",
"baseUrl": "http://192.168.1.100:5100"
}
]
Bridge 측 엔드포인트
Bridge.Host에서 제공하는 바인딩/상태 API입니다. Engine이 프록시 호출합니다.
엔드포인트 요약
| 메서드 | 경로 | 설명 |
|---|---|---|
| POST | /api/bridge/bind | 바인딩 수락 (매니페스트 반환) |
| DELETE | /api/bridge/bind | 바인딩 해제 |
| GET | /api/bridge/status | Bridge 상태 조회 |
| GET | /api/bridge/manifest | Bridge 매니페스트 조회 |
GET /api/bridge/status
Bridge의 현재 상태를 조회합니다. Heartbeat 모니터링에 사용됩니다.
응답 (200 OK):
{
"bridgeId": "bridge-001",
"status": "bound",
"boundEngineId": "engine-001",
"driverType": "ModbusTcp",
"driverId": "modbus-line1",
"version": "3.0.0"
}
GET /api/bridge/manifest
Bridge가 제공하는 태그/서브모델 매니페스트를 조회합니다.
응답 (200 OK):
{
"bridgeId": "bridge-001",
"driverType": "ModbusTcp",
"driverId": "modbus-line1",
"tags": [
{ "name": "Temperature", "address": "D100", "dataType": "Int16", "length": 2, "scanInterval": 1000 }
],
"availableSubmodels": ["OperationalData", "Nameplate", "TechnicalData"]
}
핵심 인터페이스
IBridgeDiscovery
mDNS/DNS-SD 기반 Bridge 검색 인터페이스입니다.
public interface IBridgeDiscovery
{
IObservable<BridgeInfo> DiscoveredBridges { get; }
Task<IReadOnlyList<BridgeInfo>> ScanAsync(TimeSpan timeout, CancellationToken ct = default);
}
| 멤버 | 타입 | 설명 |
|---|---|---|
DiscoveredBridges | IObservable<BridgeInfo> | 실시간 Bridge 발견 스트림 |
ScanAsync | Task<IReadOnlyList<BridgeInfo>> | 지정 시간 동안 네트워크 스캔 |
IBridgeAdvertiser
Bridge.Host가 자신을 mDNS로 광고하는 인터페이스입니다.
public interface IBridgeAdvertiser : IAsyncDisposable
{
bool IsAdvertising { get; }
Task StartAdvertisingAsync(BridgeAdvertisement advertisement, CancellationToken ct = default);
Task StopAdvertisingAsync(CancellationToken ct = default);
Task UpdateStatusAsync(BridgeBindingStatus status, string? boundEngineId = null, CancellationToken ct = default);
// v2.3.0 (FIR-038): 순수 추상 메서드 — 구현체는 diff 비교로 최소 단절 보장
Task UpdateAdvertisementAsync(BridgeAdvertisement advertisement, CancellationToken ct = default);
}
| 메서드 | 용도 |
|---|---|
StartAdvertisingAsync | mDNS 광고 시작 |
StopAdvertisingAsync | mDNS 광고 중지 |
UpdateStatusAsync | 바인딩 상태(Free/Bound) 변경 시 TXT 갱신 |
UpdateAdvertisementAsync | 광고 메타데이터 갱신. Identity 필드 불변 시 TXT diff만 적용, 변경 시 재등록 |
UpdateAdvertisementAsync가 Default Interface Method에서 순수 추상 메서드로 변경되었습니다.
커스텀 IBridgeAdvertiser 구현체가 있다면 이 메서드를 반드시 구현해야 합니다.
광고가 불필요한 환경(테스트, 단독 실행)에서는 NullBridgeAdvertiser를 DI에 등록하세요.
BridgeAdvertisement 갱신 예시 (record with 표현식)
// Updatable 필드만 변경 — 재등록 없이 TXT 레코드만 갱신
var updated = current with { FriendlyName = "Conveyor Line B", Location = "Zone-3" };
await _advertiser.UpdateAdvertisementAsync(updated, ct);
IBridgeBindingRepository
Bridge 바인딩 상태를 영속화하는 리포지토리 인터페이스입니다.
public interface IBridgeBindingRepository
{
Task<BridgeBindingState?> GetByBridgeIdAsync(string bridgeId, CancellationToken ct = default);
Task SaveAsync(BridgeBindingState binding, CancellationToken ct = default);
Task DeleteAsync(string bridgeId, CancellationToken ct = default);
Task<IReadOnlyList<BridgeBindingState>> ListAsync(CancellationToken ct = default);
Task UpdateHeartbeatAsync(string bridgeId, DateTimeOffset timestamp, CancellationToken ct = default);
}
값 객체
BridgeInfo
| 속성 | 타입 | 설명 |
|---|---|---|
BridgeId | string | Bridge UUID |
HostName | string | 호스트명 |
IpAddress | IPAddress | IP 주소 |
Port | int | REST API 포트 |
Version | string | 소프트웨어 버전 |
Status | BridgeBindingStatus | 바인딩 상태 (Free/Binding/Bound) |
DriverType | string | 드라이버 유형 |
TagCount | int | 등록된 태그 수 |
BaseUrl | string | REST API 기본 URL |
BridgeManifest
| 속성 | 타입 | 설명 |
|---|---|---|
BridgeId | string | Bridge UUID |
DriverType | string | 드라이버 유형 |
DriverId | string | 드라이버 식별자 |
Tags | IReadOnlyList<ManifestTag> | 사용 가능한 태그 목록 |
AvailableSubmodels | IReadOnlyList<string> | 사용 가능한 서브모델 유형 |
BindingToken | string? | 1회용 바인딩 토큰 |
BridgeBindingStatus (열거형)
| 값 | 설명 |
|---|---|
Free | 바인딩되지 않음 (검색 가능) |
Binding | 바인딩 진행 중 |
Bound | Engine에 바인딩됨 |
mDNS 서비스 타입
서비스 타입: _caffeine-bridge._tcp
Bridge.Host가 시작되면 이 서비스 타입으로 mDNS 광고를 시작합니다. Engine의 IBridgeDiscovery가 이 서비스 타입을 스캔하여 Bridge를 발견합니다.
바인딩 워크플로우
1. Engine → ScanAsync() → mDNS 네트워크 스캔
2. Engine → 사용자가 Bridge 선택
3. Engine → POST /api/bridge/bind → Bridge.Host
4. Bridge.Host → 바인딩 토큰 생성 + 매니페스트 반환
5. Engine → IBridgeBindingRepository.SaveAsync() → 바인딩 상태 영속화
6. Engine → BridgeHeartbeatWorker → 30초 간격 헬스 체크 시작
최종 업데이트: 2026-03-22