Caffeine.Robotics.Abstractions API
개요
Caffeine.Robotics.Abstractions는 Caffeine 로보틱스 모듈의 핵심 추상화 레이어입니다. IIoT 엣지 환경의 이동형·고정형 디바이스를 통합 관리하기 위한 인터페이스, CQRS 커맨드, Safety 계약, 디바이스 관리 인터페이스를 정의합니다.
Robotics 모듈 관계도
| 패키지 | 역할 |
|---|---|
Caffeine.Robotics.Abstractions | 인터페이스·CQRS·Safety 계약 정의 |
Caffeine.Robotics | 구체 구현 (AGV, 드론, 로봇 등) |
Caffeine.Robotics.OTA | OTA(Over-The-Air) 업데이트 관리 |
기본 정보
| 항목 | 값 |
|---|---|
| 패키지 | NEXCODE.Caffeine.Robotics.Abstractions |
| 타겟 프레임워크 | net10.0 |
| 루트 네임스페이스 | Caffeine.Robotics.Abstractions |
| 멀티테넌시 | IMultiTenantEntity 상속 (테넌트 격리 지원) |
핵심 인터페이스
IDevice
모든 관리 디바이스의 기반 인터페이스입니다.
public interface IDevice : IMultiTenantEntity
{
string DeviceId { get; }
string Name { get; }
DeviceState CurrentState { get; }
DateTimeOffset LastSeen { get; }
// Capability 패턴
bool HasCapability<T>() where T : IDeviceCapability;
T? GetCapability<T>() where T : IDeviceCapability;
IEnumerable<IDeviceCapability> GetAllCapabilities();
// 디바이스 제어
Task<CommandResult> ExecuteCommandAsync(DeviceCommand command, CancellationToken ct = default);
Task EmergencyStopAsync(string reason, CancellationToken ct = default);
}
DeviceState 열거형:
| 값 | 설명 |
|---|---|
Idle | 대기 상태 |
Active | 작업 수행 중 |
Error | 오류 상태 |
Offline | 오프라인 |
Charging | 충전 중 (이동형 디바이스) |
EmergencyStopped | 비상 정지 |
IRoboticDevice
이동형 자율 디바이스(AGV, 드론, 로봇)를 위한 인터페이스입니다.
public interface IRoboticDevice : IDevice
{
Task NavigateToAsync(string locationId, CancellationToken ct = default);
Task DockAsync(CancellationToken ct = default);
}
사용 예시:
// AGV 이동 제어
IRoboticDevice agv = deviceRegistry.GetDevice("AGV-001");
await agv.NavigateToAsync("STATION-A-03", cancellationToken);
await agv.DockAsync(cancellationToken);
IStationaryDevice
고정형 디바이스(스마트 피더, 센서 스테이션)를 위한 인터페이스입니다.
public interface IStationaryDevice : IDevice
{
string LocationId { get; }
}
Capability 패턴
디바이스의 기능을 분리된 인터페이스로 조합하는 패턴입니다. IDevice.HasCapability<T>()로 런타임에 지원 여부를 확인합니다.
지원 Capability (6종)
| 인터페이스 | 설명 | 주요 멤버 |
|---|---|---|
INavigationCapability | 위치 이동 및 경로 추적 | NavigateToAsync(), GetCurrentPositionAsync(), IsNavigating |
ISensingCapability | 센서 텔레메트리 스트림 | TelemetryStream (IObservable<DeviceTelemetry>) |
IActuationCapability | 액추에이터 제어 | (작동 인터페이스) |
IDispensingCapability | 자재 분배 제어 | (분배 인터페이스) |
IDockingCapability | 도킹/언도킹 제어 | (도킹 인터페이스) |
IDeviceCapability | 모든 Capability의 기반 인터페이스 | (마커 인터페이스) |
INavigationCapability 상세
public interface INavigationCapability : IDeviceCapability
{
Task<NavigationResult> NavigateToAsync(
Position targetPosition,
CancellationToken cancellationToken = default);
Task<Position> GetCurrentPositionAsync(
CancellationToken cancellationToken = default);
bool IsNavigating { get; }
}
// 위치 정보
public record Position(double Latitude, double Longitude, double? Altitude = null);
// 이동 결과
public record NavigationResult(bool Success, string? ErrorMessage = null, TimeSpan? Duration = null);
ISensingCapability 상세
public interface ISensingCapability : IDeviceCapability
{
IObservable<DeviceTelemetry> TelemetryStream { get; }
}
System.Reactive 기반의 실시간 텔레메트리 스트림을 제공합니다.
Capability 사용 예시
IDevice device = await deviceRegistry.GetDeviceAsync("ROBOT-001");
// Capability 존재 확인 후 사용
if (device.HasCapability<INavigationCapability>())
{
var nav = device.GetCapability<INavigationCapability>()!;
var currentPos = await nav.GetCurrentPositionAsync();
Console.WriteLine($"현재 위치: ({currentPos.Latitude}, {currentPos.Longitude})");
var result = await nav.NavigateToAsync(new Position(37.5665, 126.9780));
if (!result.Success)
Console.WriteLine($"이동 실패: {result.ErrorMessage}");
}
// 센서 구독
if (device.HasCapability<ISensingCapability>())
{
var sensing = device.GetCapability<ISensingCapability>()!;
sensing.TelemetryStream.Subscribe(telemetry =>
{
Console.WriteLine($"[{telemetry.DeviceId}] {telemetry.Key}: {telemetry.Value}");
});
}
// 모든 Capability 열거
foreach (var capability in device.GetAllCapabilities())
{
Console.WriteLine($"지원 Capability: {capability.GetType().Name}");
}
CQRS Commands
RegisterDeviceCommand
새 디바이스를 시스템에 등록합니다.
public record RegisterDeviceCommand(
string DeviceId,
string TenantId,
string Name,
string DeviceType) : ICommand;
사용 예시:
var command = new RegisterDeviceCommand(
DeviceId: "AGV-NEW-001",
TenantId: "tenant-abc",
Name: "신규 AGV 1호",
DeviceType: "AGV"
);
await mediator.Send(command, cancellationToken);
TriggerEmergencyStopCommand
특정 디바이스에 비상 정지를 명령합니다.
public record TriggerEmergencyStopCommand(
string DeviceId,
string Reason) : ICommand;
사용 예시:
// 비상 상황 감지 시
var stopCommand = new TriggerEmergencyStopCommand(
DeviceId: "AGV-001",
Reason: "인체 감지 — 구역 진입"
);
await mediator.Send(stopCommand, cancellationToken);
Safety 계약
ISafetyPolicyEngine
디바이스 커맨드 실행 전 안전 정책을 검증합니다.
public interface ISafetyPolicyEngine
{
Task<SafetyEvaluationResult> ValidateCommandAsync(
DeviceCommand command,
CancellationToken ct = default);
}
모든 커맨드는 실행 전 ISafetyPolicyEngine의 검증을 통과해야 합니다. 위험 지역 진입, 최대 속도 초과, 비인가 작업 등을 차단합니다.
SafetyEvaluationResult:
| 속성 | 타입 | 설명 |
|---|---|---|
IsAllowed | bool | 커맨드 실행 허용 여부 |
DenialReason | string? | 거부 시 사유 |
RiskLevel | RiskLevel | 위험도 (Low/Medium/High/Critical) |
디바이스 관리
IDeviceRegistry
디바이스 등록 및 조회를 담당합니다.
public interface IDeviceRegistry
{
Task<Device?> GetDeviceAsync(string deviceId, CancellationToken ct = default);
Task RegisterDeviceAsync(Device device, CancellationToken ct = default);
Task UpdateDeviceAsync(Device device, CancellationToken ct = default);
Task<IEnumerable<Device>> GetAllDevicesAsync(CancellationToken ct = default);
}
IDeviceHealthMonitor
디바이스 헬스비트 및 상태 스트리밍을 관리합니다.
public interface IDeviceHealthMonitor
{
IObservable<DeviceHealthEvent> HealthStream { get; }
Task ReportHeartbeatAsync(string deviceId, CancellationToken ct = default);
Task RegisterDeviceAsync(string deviceId, TimeSpan heartbeatTimeout, CancellationToken ct = default);
Task UnregisterDeviceAsync(string deviceId, CancellationToken ct = default);
}
DeviceHealthEvent:
public record DeviceHealthEvent(
string DeviceId,
DeviceHealthStatus Status,
string Reason,
DateTimeOffset Timestamp
);
public enum DeviceHealthStatus { Healthy, Unhealthy, Offline, Unknown }
사용 예시:
// 헬스 스트림 구독
healthMonitor.HealthStream
.Where(e => e.Status == DeviceHealthStatus.Offline)
.Subscribe(e =>
{
_logger.LogWarning("디바이스 오프라인: {DeviceId} — {Reason}", e.DeviceId, e.Reason);
// 알람 발생 또는 자동 복구 로직
});
// 디바이스 등록 (5분 타임아웃)
await healthMonitor.RegisterDeviceAsync("AGV-001", TimeSpan.FromMinutes(5), ct);
// 하트비트 전송 (드라이버 루프에서 주기적 호출)
await healthMonitor.ReportHeartbeatAsync("AGV-001", ct);
연결 계층
IDeviceTransportBridge
드라이버 레이어와 로보틱스 모듈 간의 통신 브릿지입니다.
// Caffeine.Robotics.Abstractions/Connectivity/IDeviceTransportBridge.cs
통합 시나리오
AGV 디바이스 관리 전체 흐름
// 1. DI 등록 (Program.cs)
services.AddSingleton<IDeviceRegistry, InMemoryDeviceRegistry>();
services.AddSingleton<IDeviceHealthMonitor, DeviceHealthMonitor>();
services.AddScoped<ISafetyPolicyEngine, RuleBasedSafetyEngine>();
// 2. 디바이스 등록
var device = new Device { DeviceId = "AGV-001", Name = "물류 AGV 1호" };
await deviceRegistry.RegisterDeviceAsync(device, ct);
await healthMonitor.RegisterDeviceAsync("AGV-001", TimeSpan.FromMinutes(5), ct);
// 3. 커맨드 전송 (Safety 검증 포함)
var moveCommand = new DeviceCommand("NAVIGATE", new { LocationId = "STATION-B" });
var safetyResult = await safetyEngine.ValidateCommandAsync(moveCommand, ct);
if (safetyResult.IsAllowed)
{
var agv = await deviceRegistry.GetDeviceAsync("AGV-001", ct);
await agv!.ExecuteCommandAsync(moveCommand, ct);
}
else
{
_logger.LogWarning("안전 정책 위반: {Reason}", safetyResult.DenialReason);
}