AAS 심화: Custom Provider + AASX + Protocol Server
이 튜토리얼에서는 Caffeine V3의 AAS(Asset Administration Shell) 모듈을 심층적으로 활용합니다. 커스텀 서브모델 Provider를 직접 구현하고, AASX 패키지로 내보내며, REST API 서버를 구성합니다.
개요
| 단계 | 주제 | 소요 시간 |
|---|---|---|
| 1 | 프로젝트 설정 | 5분 |
| 2 | Custom ISubmodelProvider 구현 | 20분 |
| 3 | AASX Import/Export | 15분 |
| 4 | AAS Protocol Server (REST API) | 15분 |
| 5 | 검증 및 정리 | 5분 |
사전 요구 사항:
- Tutorial 06: Hands-on Lab 완료
- .NET 10 SDK
NEXCODE.Caffeine.AASNuGet 패키지
학습 목표:
ISubmodelProvider인터페이스를 직접 구현하여 도메인 특화 서브모델을 생성할 수 있다- AASX 파일로 AAS Shell을 내보내고 다시 가져올 수 있다
- REST API로 AAS Shell CRUD를 수행할 수 있다
1단계: 프로젝트 설정
프로젝트 생성 및 패키지 설치
dotnet new web -n AasDeepDive
cd AasDeepDive
# Caffeine AAS 패키지 추가
dotnet add package NEXCODE.Caffeine.AAS
dotnet add package NEXCODE.Caffeine.Core
Program.cs 기본 구성
using Caffeine.AAS;
var builder = WebApplication.CreateBuilder(args);
// AAS 모듈 등록
builder.Services.AddCaffeineAas(aas =>
{
aas.AddSubmodelProvider<NameplateSubmodelProvider>()
.WithAasxPackageSupport(); // AASX Import/Export 활성화
});
var app = builder.Build();
app.MapGet("/", () => "AAS Deep Dive Tutorial");
app.Run();
2단계: Custom ISubmodelProvider 구현
Caffeine에는 7개의 기본 Provider가 내장되어 있습니다:
| Provider | 용도 |
|---|---|
NameplateSubmodelProvider | 장비 명판 (제조사, 시리얼) |
TechnicalDataSubmodelProvider | 기술 사양 (온도, 압력 한계) |
OperationalDataSubmodelProvider | 실시간 운영 데이터 |
AlarmConditionSubmodelProvider | 활성 알람/결함 |
MaintenanceHistorySubmodelProvider | 정비 이력 |
PredictiveMaintenanceSubmodelProvider | ML 기반 예측 정비 |
AiModelNameplateSubmodelProvider | AI 모델 메타데이터 |
이제 에너지 모니터링 서브모델을 직접 구현합니다.
EnergyMonitoringSubmodelProvider.cs
using Caffeine.Core.Abstractions.AAS;
namespace AasDeepDive;
/// <summary>
/// 에너지 모니터링 서브모델 Provider.
/// 전력 소비, 효율, CO2 배출량을 추적합니다.
/// </summary>
[SubmodelVersion("1.0", "에너지 모니터링 v1.0")]
public class EnergyMonitoringSubmodelProvider : ISubmodelProvider
{
public string SubmodelType => "EnergyMonitoring";
public string SemanticId => "urn:nexcode:caffeine:submodel:energy-monitoring:1.0";
public Task<ISubmodel> BuildSubmodelAsync(string assetId, CancellationToken ct = default)
{
// 서브모델 엘리먼트 구성
var elements = new List<ISubmodelElement>
{
new AasSubmodelElement
{
IdShort = "PowerConsumption",
SemanticId = "0173-1#02-AAP917#001", // ECLASS: 전력 소비
ValueType = "double",
Value = "2450.5" // kW
},
new AasSubmodelElement
{
IdShort = "EnergyEfficiency",
SemanticId = "urn:nexcode:energy:efficiency",
ValueType = "double",
Value = "92.3" // %
},
new AasSubmodelElement
{
IdShort = "CO2Emission",
SemanticId = "urn:nexcode:energy:co2",
ValueType = "double",
Value = "1.23" // ton/h
},
new AasSubmodelElement
{
IdShort = "MeasurementTimestamp",
SemanticId = "urn:nexcode:timestamp",
ValueType = "dateTime",
Value = DateTime.UtcNow.ToString("O")
}
};
ISubmodel submodel = new AasSubmodel
{
IdShort = $"EnergyMonitoring_{assetId}",
SemanticId = SemanticId,
Elements = elements
};
return Task.FromResult(submodel);
}
public Task<byte[]> SerializeAsync(ISubmodel submodel, CancellationToken ct = default)
{
// JSON 직렬화
var json = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(submodel);
return Task.FromResult(json);
}
public Task<ISubmodel> DeserializeAsync(byte[] data, CancellationToken ct = default)
{
var submodel = System.Text.Json.JsonSerializer.Deserialize<AasSubmodel>(data)!;
return Task.FromResult<ISubmodel>(submodel);
}
}
DI에 등록
builder.Services.AddCaffeineAas(aas =>
{
// 기본 Provider
aas.AddSubmodelProvider<NameplateSubmodelProvider>()
.AddSubmodelProvider<TechnicalDataSubmodelProvider>()
// 커스텀 Provider 추가
.AddSubmodelProvider<EnergyMonitoringSubmodelProvider>()
.WithAasxPackageSupport();
});
검증
등록된 모든 Provider를 확인하는 엔드포인트를 추가합니다:
app.MapGet("/aas/providers", (IEnumerable<ISubmodelProvider> providers) =>
providers.Select(p => new { p.SubmodelType, p.SemanticId }));
curl http://localhost:5000/aas/providers
예상 출력:
[
{ "submodelType": "Nameplate", "semanticId": "..." },
{ "submodelType": "TechnicalData", "semanticId": "..." },
{ "submodelType": "EnergyMonitoring", "semanticId": "urn:nexcode:caffeine:submodel:energy-monitoring:1.0" }
]
3단계: AASX Import/Export
AASX는 IEC 63278-2 표준 패키지 포맷입니다. ZIP 기반으로 AAS Shell과 서브모델을 하나의 파일로 묶습니다.
AASX 파일 구조
my-equipment.aasx (ZIP)
├── aas/
│ └── aas.json ← AAS Shell + 서브모델 JSON
├── _rels/
│ └── .rels ← 관계 정의
└── [Content_Types].xml ← MIME 타입 정의
AAS Shell 생성 및 AASX 내보내기
using Caffeine.AAS.Serialization;
app.MapPost("/aas/export", async (
IAasRepository repository,
AasxPackageHandler packageHandler) =>
{
// 1. AAS Shell 생성
var shell = new AasShell
{
IdShort = "CVD-Equipment-001",
GlobalAssetId = "urn:nexcode:asset:cvd:001",
AssetKind = AssetKind.Instance
};
// 2. 서브모델 연결
shell.SubmodelIds.Add("EnergyMonitoring_cvd001");
shell.SubmodelIds.Add("Nameplate_cvd001");
// 3. 저장소에 저장
await repository.SaveAsync(shell);
// 4. AASX 파일로 내보내기
var aasxBytes = await packageHandler.ExportAsync("CVD-Equipment-001");
return Results.File(aasxBytes, "application/asset-administration-shell-package",
"cvd-equipment-001.aasx");
});
AASX 가져오기
app.MapPost("/aas/import", async (
IFormFile file,
AasxPackageHandler packageHandler) =>
{
using var stream = file.OpenReadStream();
var shells = await packageHandler.ImportAsync(stream);
return Results.Ok(new
{
ImportedShells = shells.Count(),
ShellIds = shells.Select(s => s.IdShort)
});
});
테스트
# AASX 내보내기
curl -X POST http://localhost:5000/aas/export -o cvd-equipment.aasx
# AASX 가져오기
curl -X POST http://localhost:5000/aas/import \
-F "file=@cvd-equipment.aasx"
4단계: AAS Protocol Server (REST API)
Caffeine V3는 ISP-6 표준의 6개 Operation을 REST API로 노출합니다.
ISP-6 Operation 매핑
| ISP-6 Operation | HTTP Method | 경로 |
|---|---|---|
| GetAllAssetAdministrationShells | GET | /api/v3/aas/shells |
| GetAssetAdministrationShell | GET | /api/v3/aas/shells/{id} |
| PutAssetAdministrationShell | PUT | /api/v3/aas/shells/{id} |
| DeleteAssetAdministrationShell | DELETE | /api/v3/aas/shells/{id} |
| GetAllSubmodels | GET | /api/v3/aas/shells/{id}/submodels |
| GetSubmodel | GET | /api/v3/aas/submodels/{id} |
REST API 구성
// Program.cs에 AAS REST 엔드포인트 매핑
app.MapCaffeineAasEndpoints();
API 호출 실습
# 전체 AAS Shell 목록 조회
curl http://localhost:5000/api/v3/aas/shells
# 특정 Shell 조회
curl http://localhost:5000/api/v3/aas/shells/CVD-Equipment-001
# Shell의 서브모델 목록
curl http://localhost:5000/api/v3/aas/shells/CVD-Equipment-001/submodels
# Shell 삭제
curl -X DELETE http://localhost:5000/api/v3/aas/shells/CVD-Equipment-001
예상 출력 (Shell 목록):
[
{
"idShort": "CVD-Equipment-001",
"globalAssetId": "urn:nexcode:asset:cvd:001",
"assetKind": "Instance",
"submodelIds": ["EnergyMonitoring_cvd001", "Nameplate_cvd001"]
}
]
5단계: 검증 및 정리
전체 동작 검증 체크리스트
- Custom
EnergyMonitoringSubmodelProvider가 DI에 등록되어/aas/providers에 표시 -
BuildSubmodelAsync()로 서브모델이 올바르게 생성 - AASX 파일 내보내기 → 다운로드 가능
- AASX 파일 가져오기 → Shell 복원 확인
- REST API로 Shell CRUD 정상 동작
배운 내용 정리
| 항목 | 설명 |
|---|---|
ISubmodelProvider | 도메인 특화 서브모델 빌드/직렬화 인터페이스 |
AasBuilder | Fluent API로 AAS 모듈 구성 (Provider, Mapper, Serializer) |
AasxPackageHandler | IEC 63278-2 표준 AASX 패키지 Import/Export |
MapCaffeineAasEndpoints() | ISP-6 6개 Operation을 REST API로 노출 |
다음 단계
- Tutorial 08: Raspberry Pi 엣지 배포 — AAS를 엣지 디바이스에서 실행
- Tutorial 09: NuGet SCADA 시스템 — 장비 모델링 + AAS 연동
문제 해결
Q: Provider가 /aas/providers에 표시되지 않습니다.
AddCaffeineAas()블록 내에서.AddSubmodelProvider<T>()를 호출했는지 확인하세요.
Q: AASX 내보내기 시 빈 파일이 생성됩니다.
WithAasxPackageSupport()가AasBuilder에 등록되어 있는지 확인하세요.
Q: REST API 엔드포인트가 404를 반환합니다.
app.MapCaffeineAasEndpoints()가app.Run()전에 호출되어 있는지 확인하세요.