드라이버 관리 API
개요
드라이버 관리 API는 Caffeine Bridge에 로드된 드라이버(플러그인)의 목록을 조회하고, 상태를 확인하며, 재시작하는 기능을 제공합니다. SecsGem, Mitsubishi, Simulation 등 다양한 프로토콜 드라이버를 관리할 수 있습니다.
기본 정보
| 항목 | 값 |
|---|---|
| 서비스 | CaffeineApi |
| 엔드포인트 | Engine gRPC 포트 (기본: 5001) |
| 인증 | JWT 토큰 필요 |
| 권한 | RestartDriver는 Admin 역할 필요 |
| 버전 | v1 |
엔드포인트 요약
| 메서드 | 경로 | 설명 | 권한 |
|---|---|---|---|
| GET | /api/v1/drivers | 드라이버 목록 조회 | User |
| GET | /api/v1/drivers/{driver_name}/status | 드라이버 상태 조회 | User |
| POST | /api/v1/drivers/{driver_name}/restart | 드라이버 재시작 | Admin |
상세 엔드포인트
GetDrivers
등록된 드라이버 목록을 조회합니다.
gRPC 메서드
rpc GetDrivers (GetDriversRequest) returns (GetDriversResponse)
요청
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| type_filter | string | N | 드라이버 타입 필터 (예: "SecsGem") |
응답
{
"drivers": [
{
"name": "Sim-PLC-01",
"type": "Simulation",
"status": "Running",
"startedAt": "2026-02-17T09:00:00Z",
"tagCount": 523,
"metadata": {
"version": "1.0.0",
"protocol": "Modbus TCP"
}
},
{
"name": "SECS-Equipment-01",
"type": "SecsGem",
"status": "Running",
"startedAt": "2026-02-17T09:00:00Z",
"tagCount": 1247,
"metadata": {
"deviceId": "1",
"port": "5000"
}
}
]
}
응답 코드
| 코드 | 설명 |
|---|---|
| OK | 성공 |
| UNAUTHENTICATED | 인증 필요 |
REST 경로
GET /api/v1/drivers?typeFilter=SecsGem
GetDriverStatus
특정 드라이버의 상세 상태를 조회합니다.
gRPC 메서드
rpc GetDriverStatus (GetDriverStatusRequest) returns (GetDriverStatusResponse)
요청
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| driver_name | string | Y | 드라이버 이름 |
응답
{
"driver": {
"name": "Sim-PLC-01",
"type": "Simulation",
"status": "Running",
"startedAt": "2026-02-17T09:00:00Z",
"tagCount": 523,
"metadata": {
"version": "1.0.0"
}
},
"extendedInfo": {
"connectionState": "Connected",
"lastCommunication": "2026-02-17T10:30:00Z",
"errorCount": "0",
"averageLatency": "5ms",
"lastError": null
}
}
응답 코드
| 코드 | 설명 |
|---|---|
| OK | 성공 |
| NOT_FOUND | 드라이버를 찾을 수 없음 |
| UNAUTHENTICATED | 인증 필요 |
REST 경로
GET /api/v1/drivers/{driver_name}/status
RestartDriver
드라이버를 재시작합니다. (Admin 권한 필요)
gRPC 메서드
rpc RestartDriver (RestartDriverRequest) returns (RestartDriverResponse)
요청
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| driver_name | string | Y | 드라이버 이름 |
응답
{
"success": true,
"message": "Driver 'Sim-PLC-01' restarted successfully"
}
응답 코드
| 코드 | 설명 |
|---|---|
| OK | 성공 |
| NOT_FOUND | 드라이버를 찾을 수 없음 |
| PERMISSION_DENIED | Admin 권한 필요 |
| FAILED_PRECONDITION | 재시작 실패 (예: 드라이버 오류) |
REST 경로
POST /api/v1/drivers/{driver_name}/restart
데이터 타입
DriverInfo 구조
| 필드 | 타입 | 설명 |
|---|---|---|
| name | string | 드라이버 이름 (고유 식별자) |
| type | string | 드라이버 타입 |
| status | string | 상태 ("Running", "Stopped", "Error") |
| started_at | Timestamp | 시작 시각 |
| tag_count | int32 | 등록된 태그 수 |
| metadata | map<string, string> | 추가 메타데이터 |
Driver Type 값
| 값 | 설명 |
|---|---|
| SecsGem | SEMI SECS/GEM 프로토콜 |
| Simulation | 시뮬레이션 드라이버 |
| Mitsubishi | 미쓰비시 PLC |
| Omron | 오므론 PLC |
| Lse | LS Electric (구 LS산전) |
| ModbusTcp (v2.0.5) | Modbus TCP 드라이버 (FluentModbus 기반) |
| ModbusRtu (v2.0.5) | Modbus RTU 드라이버 — RS-485 시리얼 (FluentModbus + System.IO.Ports) |
Status 값
| 값 | 설명 |
|---|---|
| Running | 정상 동작 중 |
| Stopped | 정지 상태 |
| Error | 오류 상태 |
Extended Info 필드
GetDriverStatus의 extendedInfo 맵에 포함될 수 있는 필드:
| 키 | 설명 | 예시 값 |
|---|---|---|
| connectionState | 연결 상태 | "Connected", "Disconnected" |
| lastCommunication | 마지막 통신 시각 | "2026-02-17T10:30:00Z" |
| errorCount | 누적 에러 횟수 | "0", "5" |
| averageLatency | 평균 응답 시간 | "5ms", "12ms" |
| lastError | 마지막 에러 메시지 | null, "Connection timeout" |
| ipAddress | 장비 IP 주소 | "192.168.1.100" |
| port | 통신 포트 | "5000" |
사용 예제
C# (CaffeineClient)
using Caffeine.Client;
var client = new CaffeineClient();
await client.ConnectAsync("https://localhost:5001");
// 드라이버 목록 조회
var driversResponse = await client.GetDriversAsync();
foreach (var driver in driversResponse.Drivers)
{
Console.WriteLine($"{driver.Name} ({driver.Type}): {driver.Status} - {driver.TagCount} tags");
}
// 특정 타입 필터
var secsDrivers = await client.GetDriversAsync(typeFilter: "SecsGem");
// 드라이버 상태 조회
var statusResponse = await client.GetDriverStatusAsync("Sim-PLC-01");
Console.WriteLine($"Status: {statusResponse.Driver.Status}");
Console.WriteLine($"Connection: {statusResponse.ExtendedInfo["connectionState"]}");
Console.WriteLine($"Last Communication: {statusResponse.ExtendedInfo["lastCommunication"]}");
// 드라이버 재시작 (Admin 권한 필요)
var restartResponse = await client.RestartDriverAsync("Sim-PLC-01");
if (restartResponse.Success)
{
Console.WriteLine($"Restarted: {restartResponse.Message}");
}
await client.DisposeAsync();
C# (직접 gRPC 호출)
using Grpc.Net.Client;
using Caffeine.IPC.Grpc;
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var grpcClient = new CaffeineApi.CaffeineApiClient(channel);
// 드라이버 목록 조회
var driversResponse = await grpcClient.GetDriversAsync(new GetDriversRequest
{
TypeFilter = "SecsGem"
});
foreach (var driver in driversResponse.Drivers)
{
Console.WriteLine($"{driver.Name}: {driver.Status} ({driver.TagCount} tags)");
}
// 드라이버 상태 조회
var statusResponse = await grpcClient.GetDriverStatusAsync(new GetDriverStatusRequest
{
DriverName = "Sim-PLC-01"
});
Console.WriteLine($"Driver: {statusResponse.Driver.Name}");
Console.WriteLine($"Status: {statusResponse.Driver.Status}");
Console.WriteLine($"Extended Info:");
foreach (var kvp in statusResponse.ExtendedInfo)
{
Console.WriteLine($" {kvp.Key}: {kvp.Value}");
}
// 드라이버 재시작 (Admin JWT 필요)
var headers = new Metadata
{
{ "Authorization", $"Bearer {adminJwtToken}" }
};
var restartResponse = await grpcClient.RestartDriverAsync(
new RestartDriverRequest { DriverName = "Sim-PLC-01" },
headers
);
Console.WriteLine($"Restart: {restartResponse.Success}, {restartResponse.Message}");
curl (REST API)
드라이버 목록 조회
curl -X GET "https://localhost:5001/api/v1/drivers" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
응답
{
"drivers": [
{
"name": "Sim-PLC-01",
"type": "Simulation",
"status": "Running",
"startedAt": "2026-02-17T09:00:00.000Z",
"tagCount": 523,
"metadata": {
"version": "1.0.0"
}
}
]
}
특정 타입 필터
curl -X GET "https://localhost:5001/api/v1/drivers?typeFilter=SecsGem" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
드라이버 상태 조회
curl -X GET "https://localhost:5001/api/v1/drivers/Sim-PLC-01/status" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
응답
{
"driver": {
"name": "Sim-PLC-01",
"type": "Simulation",
"status": "Running",
"startedAt": "2026-02-17T09:00:00.000Z",
"tagCount": 523,
"metadata": {
"version": "1.0.0"
}
},
"extendedInfo": {
"connectionState": "Connected",
"lastCommunication": "2026-02-17T10:30:00Z",
"errorCount": "0",
"averageLatency": "5ms"
}
}
드라이버 재시작
curl -X POST "https://localhost:5001/api/v1/drivers/Sim-PLC-01/restart" \
-H "Authorization: Bearer ADMIN_JWT_TOKEN"
응답
{
"success": true,
"message": "Driver 'Sim-PLC-01' restarted successfully"
}
에러 코드
| 코드 | 설명 | HTTP 상태 |
|---|---|---|
| DRIVER_NOT_FOUND | 드라이버를 찾을 수 없음 | 404 |
| PERMISSION_DENIED | Admin 권한 필요 | 403 |
| RESTART_FAILED | 드라이버 재시작 실패 | 500 |
| DRIVER_ALREADY_STOPPED | 이미 정지된 드라이버 | 409 |
드라이버 상태 모니터링
상태 확인 주기
프로덕션 환경에서는 주기적으로 드라이버 상태를 확인하는 것이 권장됩니다.
using System.Timers;
var timer = new Timer(5000); // 5초마다
timer.Elapsed += async (sender, e) =>
{
var statusResponse = await client.GetDriverStatusAsync("Sim-PLC-01");
if (statusResponse.Driver.Status != "Running")
{
Console.WriteLine($"⚠️ Driver {statusResponse.Driver.Name} is {statusResponse.Driver.Status}");
// 알림 발송 또는 자동 재시작 로직
}
};
timer.Start();
Health Check 통합
ASP.NET Core Health Check에 드라이버 상태를 통합할 수 있습니다.
public class DriverHealthCheck : IHealthCheck
{
private readonly CaffeineClient _client;
public DriverHealthCheck(CaffeineClient client)
{
_client = client;
}
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
var driversResponse = await _client.GetDriversAsync();
var unhealthyDrivers = driversResponse.Drivers
.Where(d => d.Status != "Running")
.ToList();
if (unhealthyDrivers.Any())
{
var driverNames = string.Join(", ", unhealthyDrivers.Select(d => d.Name));
return HealthCheckResult.Unhealthy($"Drivers unhealthy: {driverNames}");
}
return HealthCheckResult.Healthy($"{driversResponse.Drivers.Count} drivers running");
}
}
문제 해결
드라이버를 찾을 수 없음 (DRIVER_NOT_FOUND)
원인:
- 드라이버 이름 오타
- 드라이버가 Bridge에 로드되지 않음
- 드라이버 설정 파일 누락
해결:
GetDrivers로 등록된 드라이버 목록 확인- Bridge의
driver_settings.json확인 - Bridge 로그에서 드라이버 로드 오류 확인
# Bridge 로그 확인
tail -f /var/log/caffeine/bridge.log | grep "Driver"
드라이버 재시작 실패 (RESTART_FAILED)
원인:
- 드라이버 내부 오류
- 장비 연결 실패
- 포트 충돌
해결:
GetDriverStatus로extendedInfo.lastError확인- 장비 네트워크 연결 확인 (ping, telnet)
- 포트 충돌 확인 (
netstat -an | grep {port}) - Bridge 완전 재시작 시도
권한 거부 (PERMISSION_DENIED)
원인:
- Admin 역할이 없는 JWT 토큰
- JWT 토큰 만료
해결:
- JWT 토큰의
role클레임 확인 - Admin 권한 부여 요청
- JWT 토큰 갱신
// JWT 토큰 디코딩 (검증용)
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadJwtToken(jwtToken);
var roleClaim = token.Claims.FirstOrDefault(c => c.Type == "role");
Console.WriteLine($"Role: {roleClaim?.Value}");
드라이버 상태가 "Error"
원인:
- 장비 통신 오류
- 설정 파일 오류
- 드라이버 버그
해결:
extendedInfo.lastError확인- 장비 전원 및 네트워크 확인
- 드라이버 설정 검증
- 드라이버 재시작 시도
- Bridge 로그 상세 확인
성능 최적화
드라이버 목록 캐싱
드라이버 목록은 자주 변경되지 않으므로 캐싱을 사용합니다.
using Microsoft.Extensions.Caching.Memory;
private readonly IMemoryCache _cache;
private const string CacheKey = "drivers_list";
public async Task<GetDriversResponse> GetDriversWithCacheAsync()
{
if (_cache.TryGetValue(CacheKey, out GetDriversResponse cachedResponse))
{
return cachedResponse;
}
var response = await client.GetDriversAsync();
_cache.Set(CacheKey, response, TimeSpan.FromMinutes(5));
return response;
}
병렬 상태 조회
여러 드라이버 상태를 병렬로 조회합니다.
var driverNames = new[] { "Sim-PLC-01", "SECS-Equipment-01", "Modbus-Device-01" };
var statusTasks = driverNames.Select(name => client.GetDriverStatusAsync(name));
var statuses = await Task.WhenAll(statusTasks);
foreach (var status in statuses)
{
Console.WriteLine($"{status.Driver.Name}: {status.Driver.Status}");
}
관련 문서
REST API
Caffeine Engine의 REST 엔드포인트(
DriverController)를 통해 HTTP로 드라이버 설정을 관리할 수 있습니다.
기본 정보
| 항목 | 값 |
|---|---|
| 기본 경로 | /api/v1.0/drivers |
| 인증 | 불필요 |
엔드포인트 목록
| 메서드 | 경로 | 설명 |
|---|---|---|
| GET | /api/v1.0/drivers | 드라이버 설정 목록 조회 |
| POST | /api/v1.0/drivers | 드라이버 설정 업데이트 |
GET /api/v1.0/drivers
전체 드라이버 설정을 조회합니다.
응답
[
{
"id": "Equipment-01",
"driverType": "Modbus",
"ipAddress": "192.168.1.10",
"port": 502,
"pollingInterval": 1000
}
]
POST /api/v1.0/drivers
드라이버 설정을 업데이트합니다. 설정 변경 후 Engine 재시작이 필요합니다.
요청 본문: DriverConfig[] 배열 (JSON)
응답: { "message": "Configuration saved. Restart Engine to apply changes." }
응답 코드: 200 저장 성공, 400 빈 목록, 500 파일 쓰기 실패
REST DTO
DriverConfig
| 속성 | 타입 | 설명 |
|---|---|---|
| id | string | 드라이버 고유 식별자 |
| driverType | string | 드라이버 타입 (Modbus, SecsGem, Simulation 등) |
| ipAddress | string | 장비 IP 주소 |
| port | int | 통신 포트 번호 |
| pollingInterval | int | 폴링 주기 (밀리초) |
지원 드라이버 타입
| 드라이버 타입 | 설명 | 기본 포트 | 패키지 |
|---|---|---|---|
| ModbusTcp (v2.0.5) | Modbus TCP 프로토콜 | 502 | Caffeine.Drivers.ModbusTcp |
| ModbusRtu (v2.0.5) | Modbus RTU (RS-485 시리얼) | COM1 | Caffeine.Drivers.ModbusRtu |
| SecsGem | SECS/GEM (SEMI E5/E30) | 5000 | Caffeine.Drivers.SecsGem |
| Lse | LS Electric PLC | 2004 | Caffeine.Drivers.Lse |
| Mitsubishi | Mitsubishi PLC (MC Protocol) | 5000 | Caffeine.Drivers.Mitsubishi |
| Omron | Omron FINS 프로토콜 | 9600 | Caffeine.Drivers.Omron |
| Simulation | 시뮬레이션 드라이버 (테스트용) | 0 | Caffeine.Drivers.Simulation |
라이선스별 드라이버 제한
| 등급 | 최대 드라이버 |
|---|---|
| Community | 2개 |
| Professional | 10개 |
| Enterprise | 무제한 |
설정 파일 위치: {ContentRootPath}/config/equipment_model.json (또는 CAFFEINE_MODEL_PATH 환경변수)
IDriverCapabilities
드라이버의 선택적 고급 기능을 런타임에 조회하기 위한 인터페이스입니다. 모든 드라이버가 구현할 필요는 없으며, is 패턴 매칭으로 지원 여부를 확인합니다.
위치: src/Caffeine.Core/Abstractions/Drivers/IDriverCapabilities.cs
public interface IDriverCapabilities
{
/// <summary>드라이버가 지원하는 통신 프로토콜 목록 (예: "ModbusTCP", "MQTT", "OPC-UA")</summary>
IReadOnlyList<string> SupportedProtocols { get; }
/// <summary>최대 초당 처리 메시지 수 (0 = 무제한)</summary>
int MaxMessagesPerSecond { get; }
/// <summary>오프라인 버퍼링 지원 여부</summary>
bool SupportsOfflineBuffering { get; }
/// <summary>쌍방향 명령/응답 통신 지원 여부</summary>
bool SupportsBidirectional { get; }
/// <summary>드라이버가 읽을 수 있는 센서 카테고리 목록</summary>
IReadOnlyList<string> SupportedSensorCategories { get; }
}
속성
| 속성 | 타입 | 설명 |
|---|---|---|
SupportedProtocols | IReadOnlyList<string> | 지원 프로토콜 목록 (예: ["ModbusTCP"]) |
MaxMessagesPerSecond | int | 초당 최대 처리 메시지 수 (0 = 무제한) |
SupportsOfflineBuffering | bool | 오프라인 버퍼링 지원 여부 |
SupportsBidirectional | bool | 읽기/쓰기 양방향 통신 지원 여부 |
SupportedSensorCategories | IReadOnlyList<string> | 읽을 수 있는 SensorCategory 이름 목록 |
드라이버별 지원 현황
| 드라이버 | 프로토콜 | MaxMsg/s | OfflineBuffer | Bidirectional |
|---|---|---|---|---|
| Simulation | Simulation | 0(무제한) | ❌ | ✅ |
| SecsGem | SECS/GEM | 0(무제한) | ❌ | ✅ |
| Mitsubishi | MC Protocol | 0(무제한) | ❌ | ✅ |
| Omron | FINS | 0(무제한) | ❌ | ✅ |
| Lse | XGT | 0(무제한) | ❌ | ✅ |
| ModbusTcp (v2.0.5) | ModbusTCP | 100 | ❌ | ✅ |
| ModbusRtu (v2.0.5) | ModbusRTU | 20 | ❌ | ✅ |
ModbusRtu
MaxMessagesPerSecond = 20: RS-485 시리얼 대역폭 제한으로 TCP(100)보다 낮게 설정됩니다.
사용 예시
// IDriverCapabilities 지원 여부 확인
if (driver is IDriverCapabilities caps)
{
Console.WriteLine($"Protocols: {string.Join(", ", caps.SupportedProtocols)}");
Console.WriteLine($"Max msg/s: {caps.MaxMessagesPerSecond}");
Console.WriteLine($"Bidirectional: {caps.SupportsBidirectional}");
Console.WriteLine($"Sensor categories: {string.Join(", ", caps.SupportedSensorCategories)}");
}
// 양방향 지원 드라이버에만 쓰기 수행
if (driver is IDriverCapabilities { SupportsBidirectional: true })
{
await driver.WriteTagAsync(tagId, value, ct);
}
참고
- Caffeine.Core API — IDriverCapabilities — 인터페이스 원본 정의
- Edge↔Cloud 통신 API — 전송 추상화 상세
- Modbus 드라이버 개발 가이드 — ModbusTcp/ModbusRtu 사용법