본문으로 건너뛰기

Caffeine.Robotics.Abstractions API

개요​

Caffeine.Robotics.Abstractions는 Caffeine 로보틱스 모듈의 핵심 추상화 레이어입니다. IIoT 엣지 환경의 이동형·고정형 디바이스를 통합 관리하기 위한 인터페이스, CQRS 커맨드, Safety 계약, 디바이스 관리 인터페이스를 정의합니다.

Robotics 모듈 관계도​

패키지역할
Caffeine.Robotics.Abstractions인터페이스·CQRS·Safety 계약 정의
Caffeine.Robotics구체 구현 (AGV, 드론, 로봇 등)
Caffeine.Robotics.OTAOTA(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 계약​

IRoboticsSafetyPolicyEngine​

디바이스 커맨드 실행 전 안전 정책을 검증합니다. Caffeine.Core의 ISafetyPolicyEngine을 상속하며 로보틱스 전용 DeviceCommand 검증을 추가합니다.

// Caffeine.Robotics.Abstractions.Domain.Services
public interface IRoboticsSafetyPolicyEngine : Caffeine.Core.Abstractions.Safety.ISafetyPolicyEngine
{
// Core 상속: Task<SafetyEvaluationResult> ValidateAsync(OperationRequest, CancellationToken)
// 로보틱스 전용 추가 메서드:
Task<SafetyEvaluationResult> ValidateCommandAsync(
DeviceCommand command,
CancellationToken ct = default);
}

하위호환 별칭: Robotics 네임스페이스 내 ISafetyPolicyEngine은 IRoboticsSafetyPolicyEngine의 별칭으로 유지됩니다.

모든 커맨드는 실행 전 IRoboticsSafetyPolicyEngine의 검증을 통과해야 합니다. 위험 지역 진입, 최대 속도 초과, 비인가 작업 등을 차단합니다.

SafetyEvaluationResult (Core 단일 소스 — Caffeine.Core.Abstractions.Safety):

속성타입설명
IsAllowedbool커맨드 실행 허용 여부
DenialReasonstring?거부 시 사유
RiskLevelRiskLevel위험도 (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

DeviceTransportBridgeBase (FIR-020)​

DeviceTransportBridge의 보일러플레이트를 제거하고 커스텀 확장을 지원하는 추상 Base 클래스입니다.

// Caffeine.Robotics/Connectivity/DeviceTransportBridgeBase.cs
public abstract class DeviceTransportBridgeBase : IDeviceTransportBridge
{
// 4개 virtual 확장 포인트 — 필요한 것만 override
protected virtual string BuildTelemetryTopic(string deviceId) => $"devices/{deviceId}/telemetry";
protected virtual string BuildStatusTopic(string deviceId) => $"devices/{deviceId}/status";
protected virtual string BuildCommandTopicFilter(string deviceId) => $"devices/{deviceId}/commands/#";
protected virtual byte[] SerializeTelemetry(DeviceTelemetry telemetry) => JsonSerializer.SerializeToUtf8Bytes(telemetry);
}

커스텀 확장 예시 (NexFarm 패턴 — 농업용 센서 토픽 구조):

public class NexFarmTransportBridge : DeviceTransportBridgeBase
{
// 농업용 토픽 구조로 오버라이드
protected override string BuildTelemetryTopic(string deviceId)
=> $"farm/{_tenantId}/sensors/{deviceId}/telemetry";

protected override string BuildCommandTopicFilter(string deviceId)
=> $"farm/{_tenantId}/actuators/{deviceId}/+";
}

DeviceTransportBridgeBase를 상속하면 변경이 필요한 메서드만 override하고 나머지는 기본 구현을 그대로 사용할 수 있습니다.

통합 시나리오​

AGV 디바이스 관리 전체 흐름​

// 1. DI 등록 (Program.cs)
services.AddSingleton<IDeviceRegistry, InMemoryDeviceRegistry>();
services.AddSingleton<IDeviceHealthMonitor, DeviceHealthMonitor>();
// FIR-019: 이중 등록 패턴 — IRoboticsSafetyPolicyEngine + Core.ISafetyPolicyEngine
services.AddScoped<IRoboticsSafetyPolicyEngine, RuleBasedSafetyEngine>();
services.AddScoped<Caffeine.Core.Abstractions.Safety.ISafetyPolicyEngine>(
sp => sp.GetRequiredService<IRoboticsSafetyPolicyEngine>());

// 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);
}

관련 문서​