18. Sammlung von Integrationsbeispielen
Hier sind End-to-End-Szenarien zusammengestellt, die mit dem V5-Client häufig durchgeführt werden.
18.1 Inbetriebnahme eines neuen Werks — Registrierung aller Stammdaten
Szenario zum Registrieren eines neuen Standorts und zum Einrichten des Anlagenbaums (Area → Line → Equipment) → OPC → Tag in einem Durchgang.
import plantpulse.api.v5.APIClient_V5;
import plantpulse.api.v5.dto.request.*;
import plantpulse.api.v5.dto.response.*;
public class BootstrapNewSite {
public static void main(String[] args) throws Exception {
try (APIClient_V5 client = new APIClient_V5(
"HTTPS", "100.68.69.41", 7443, "api", TOKEN, false)) {
client.connect();
// 1) 사이트 등록
SiteRequestV5 site = new SiteRequestV5();
site.setSite_id("SITE_DJ");
site.setSite_name("대전 1공장");
site.setLat("36.3504");
site.setLng("127.3845");
client.site().create(site);
// 2) Area
AssetRequestV5 area = new AssetRequestV5();
area.setSite_id("SITE_DJ");
area.setParent_asset_id("SITE_DJ");
area.setAsset_id("ASSET_DJ_A_0001");
area.setAsset_name("조립구역");
area.setAsset_type("A");
client.asset().create(area);
// 3) Line
AssetRequestV5 line = new AssetRequestV5();
line.setSite_id("SITE_DJ");
line.setParent_asset_id("ASSET_DJ_A_0001");
line.setAsset_id("ASSET_DJ_L_0001");
line.setAsset_name("1번 조립 라인");
line.setAsset_type("L");
client.asset().create(line);
// 4) Equipment (3대)
for (int i = 1; i <= 3; i++) {
AssetRequestV5 eq = new AssetRequestV5();
eq.setSite_id("SITE_DJ");
eq.setParent_asset_id("ASSET_DJ_L_0001");
eq.setAsset_id(String.format("ASSET_DJ_M_%04d", i));
eq.setAsset_name("CNC #" + i);
eq.setAsset_type("M");
eq.setTable_type("CNC");
client.asset().create(eq);
}
// 5) OPC (PLC)
OPCRequestV5 plc = new OPCRequestV5();
plc.setOpc_id("OPC_PLC_L1_001");
plc.setOpc_name("Line1 메인 PLC");
plc.setOpc_type("PLC");
plc.setOpc_sub_type("SIEMENS_S7");
plc.setSite_id("SITE_DJ");
plc.setOpc_server_ip("192.168.10.50");
plc.setOpc_agent_ip("192.168.10.5");
plc.setOpc_agent_port(60000);
client.opc().create(plc);
// 6) Tag (각 Equipment에 RPM/온도/카운트 3개씩)
for (int i = 1; i <= 3; i++) {
String equipId = String.format("ASSET_DJ_M_%04d", i);
for (String metric : new String[]{"RPM", "TEMP", "COUNT"}) {
TagRequestV5 tag = new TagRequestV5();
tag.setSite_id("SITE_DJ");
tag.setOpc_id("OPC_PLC_L1_001");
tag.setLinked_asset_id(equipId);
tag.setTag_id(String.format("TAG_PLC_L1_M%04d_%s", i, metric));
tag.setTag_name("CNC#" + i + " " + metric);
tag.setTag_source("OPC");
tag.setJava_type("Double");
tag.setUnit(metricUnit(metric));
client.tag().create(tag);
}
}
System.out.println("부팅 완료");
}
}
private static String metricUnit(String m) {
switch (m) {
case "RPM": return "RPM";
case "TEMP": return "C";
case "COUNT": return "EA";
default: return "";
}
}
}
18.2 ERP-Stammdatensynchronisation (idempotent)
Synchronisation der aus dem ERP übernommenen Kunden-, Produkt- und Mitarbeiterdaten in die V5-Stammdaten. Nach Prüfung von exists() Verzweigung nach create() oder update().
public static void syncCustomers(APIClient_V5 client, List<ErpCustomer> erpData) {
for (ErpCustomer erp : erpData) {
String customerId = "CUST_" + erp.code;
CustomerRequestV5 req = new CustomerRequestV5();
req.setCustomer_id(customerId);
req.setCustomer_name(erp.name);
req.setSite_id(erp.siteId);
req.setExternal_customer_id(erp.id);
req.setCompany_code(erp.companyCode);
req.setManager_name(erp.contact);
req.setManager_email(erp.email);
if (client.customer().exists(customerId)) {
client.customer().update(customerId, req);
} else {
client.customer().create(req);
}
}
}
18.3 MES-Arbeitsauftrag anlegen, starten und abschließen
public static void runMesWorkOrder(APIClient_V5 client, MesOrder mes) {
// 1) Order 생성
String orderId = "ORD_" + mes.date + "_" + String.format("%03d", mes.seq);
OrderRequestV5 req = new OrderRequestV5();
req.setOrder_id(orderId);
req.setTitle(mes.title);
req.setSite_id("SITE_DJ");
req.setAsset_id(mes.equipId);
req.setProduct_id(mes.productId);
req.setEmployee_id(mes.operatorId);
req.setCustomer_id(mes.customerId);
req.setTarget_units(mes.qty);
req.setUnit("EA");
req.setTime_per_unit_in_ms(mes.cycleTime);
req.setFix_start_timestamp(mes.plannedStart);
req.setFix_end_timestamp(mes.plannedEnd);
req.setExternal_order_id(mes.mesOrderId);
OrderResponseV5 created = client.order().create(req);
if (created == null) {
log.error("작업지시 생성 실패: " + orderId);
return;
}
// 2) 작업 시작 (생성 직후가 아닌, 라인 IDLE 신호 받은 시점에 호출)
if (!client.order().start(orderId)) {
log.warn("시작 실패 — 현재 상태 확인 필요");
return;
}
// 3) 작업 진행 모니터링 ...
// ...
// 4) 작업 종료
client.order().end(orderId);
}
18.4 Health-Dashboard für die gesamte Linie
Datensammler für ein Dashboard, der die Anzahl der Tags, die letzten Alarme und den aktuellen Arbeitsauftrag der Equipment unterhalb einer Linie in einem Schritt zusammenführt.
import java.util.List;
import java.util.stream.Collectors;
public static DashboardSnapshot collectLineHealth(APIClient_V5 client, String lineId) {
DashboardSnapshot snap = new DashboardSnapshot();
// 1) Line 산하 Equipment
List<AssetResponseV5> equipments = client.asset().list().stream()
.filter(a -> lineId.equals(a.getParent_asset_id()))
.filter(a -> "M".equals(a.getAsset_type()))
.collect(Collectors.toList());
snap.equipmentCount = equipments.size();
// 2) 각 Equipment 통계
for (AssetResponseV5 eq : equipments) {
long tagCount = client.tag().countByAsset(eq.getAsset_id());
snap.totalTags += tagCount;
PathResponseV5 path = client.path().getByAsset(eq.getAsset_id());
snap.paths.put(eq.getAsset_id(),
String.format("%s > %s > %s > %s",
path.getSite_name(), path.getArea_name(),
path.getLine_name(), path.getEquipment_name()));
}
// 3) 최근 알람 — 라인 산하 자산만
List<String> assetIds = equipments.stream()
.map(AssetResponseV5::getAsset_id)
.collect(Collectors.toList());
snap.recentAlarms = client.alarm().list(200).stream()
.filter(a -> a.getTag_location() != null
&& assetIds.stream()
.anyMatch(id -> a.getTag_location().contains(id)))
.collect(Collectors.toList());
// 4) 현재 진행중 작업지시
snap.activeOrders = client.order().list().stream()
.filter(o -> "START".equals(o.getStatus())
|| "PAUSE".equals(o.getStatus()))
.filter(o -> assetIds.contains(o.getAsset_id()))
.collect(Collectors.toList());
return snap;
}
18.5 Alarmschwellwerte für Tags gesammelt anwenden
Anwendung von Alarmschwellwerten auf alle Temperatur-Tags einer bestimmten Linie im Zuge einer Änderung der Betriebsbedingungen.
public static void applyTempAlarmsToLine(APIClient_V5 client, String lineId) {
// 1) 라인 산하 Equipment
List<AssetResponseV5> equipments = client.asset().list().stream()
.filter(a -> lineId.equals(a.getParent_asset_id()))
.collect(Collectors.toList());
// 2) 각 Equipment의 온도 태그
for (AssetResponseV5 eq : equipments) {
List<TagResponseV5> tempTags = client.tag().listByAsset(eq.getAsset_id())
.stream()
.filter(t -> "C".equals(t.getUnit()))
.collect(Collectors.toList());
// 3) patchAlarm으로 임계치 일괄 적용 (서버에서 CEP/EQL 재배포)
for (TagResponseV5 t : tempTags) {
TagAlarmPatchRequestV5 alarm = new TagAlarmPatchRequestV5();
alarm.setUse_alarm("Y");
alarm.setLo_lo("0");
alarm.setLo("10");
alarm.setHi("80");
alarm.setHi_hi("95");
alarm.setTrip_hi("100");
alarm.setDuplicate_check_minutes(5);
alarm.setSend_email("Y");
client.tag().patchAlarm(t.getTag_id(), alarm);
}
}
}
18.6 Konfliktprüfung zwischen Inspektionsplan und Arbeitsauftrag
import java.util.Set;
import java.util.stream.Collectors;
public static List<OrderResponseV5> conflictsWithMaintenance(
APIClient_V5 client, String fromIso, String toIso) {
// 1) 점검 일정의 자산 ID 집합
Set<String> mtnAssets = client.calendar().search(fromIso, toIso).stream()
.filter(c -> "MTN".equals(c.getTarget_type()))
.map(CalendarResponseV5::getAsset_id)
.collect(Collectors.toSet());
// 2) 같은 기간 진행중·예정 작업지시 중 점검 대상 자산
return client.order().list().stream()
.filter(o -> "WAIT".equals(o.getStatus()) || "START".equals(o.getStatus()))
.filter(o -> mtnAssets.contains(o.getAsset_id()))
.collect(Collectors.toList());
}
18.7 Flow-Health-Benachrichtigung
Monitoring-Worker, der eine Benachrichtigung sendet, sobald ein Flow mit Fehler erkannt wird.
public static void monitorFlowHealth(APIClient_V5 client) throws InterruptedException {
Map<String, Long> lastErrorCounts = new HashMap<>();
while (true) {
for (FlowResponseV5 flow : client.flow().list()) {
long prev = lastErrorCounts.getOrDefault(flow.getFlow_id(), 0L);
long curr = flow.getError_count();
if (curr > prev) {
long newErrors = curr - prev;
sendAlert(String.format("Flow %s 에서 신규 에러 %d건 발생",
flow.getFlow_name(), newErrors));
}
lastErrorCounts.put(flow.getFlow_id(), curr);
}
Thread.sleep(30_000);
}
}
18.8 Tag-CUD-Lebenszyklus (Testcode-Muster)
Ein in Smoke- oder Integrationstests häufig verwendetes Muster — Anlegen/Abfragen/Ändern/Löschen bis hin zur Bereinigung isolierter Ressourcen.
public static void tagCudCycle(APIClient_V5 client) {
long ts = System.currentTimeMillis();
String opcId = "OPC_TEST_" + ts;
String tagId = "TAG_TEST_" + ts;
// 1) 부모 OPC 생성 (Tag의 FK)
OPCRequestV5 opcReq = new OPCRequestV5();
opcReq.setOpc_id(opcId);
opcReq.setOpc_name("test-opc-" + ts);
opcReq.setOpc_type("VIRTUAL");
opcReq.setSite_id("SITE_00001");
opcReq.setOpc_agent_ip("127.0.0.1");
opcReq.setOpc_agent_port(0);
client.opc().create(opcReq);
try {
// 2) Tag 생성
TagRequestV5 req = new TagRequestV5();
req.setTag_id(tagId);
req.setTag_name("test-tag-" + ts);
req.setOpc_id(opcId);
req.setSite_id("SITE_00001");
req.setJava_type("Double");
req.setUnit("C");
req.setTag_source("OPC");
TagResponseV5 created = client.tag().create(req);
assert created != null;
// 3) 조회 + 존재 확인
TagResponseV5 fetched = client.tag().get(tagId);
assert fetched != null;
assert client.tag().exists(tagId);
// 4) 수정
req.setTag_name("test-tag-" + ts + "-updated");
TagResponseV5 updated = client.tag().update(tagId, req);
assert updated != null;
assert req.getTag_name().equals(updated.getTag_name());
// 5) 삭제
assert client.tag().delete(tagId);
assert !client.tag().exists(tagId);
} finally {
// 6) 임시 OPC 정리
client.opc().delete(opcId);
}
}
Nächste Schritte
- Detaillierte Handbücher nach Domäne wählen Sie im Inhaltsverzeichnis der README aus.
- Es empfiehlt sich, die Konstruktionsprinzipien für IDs unter Domänen-ID-Regeln noch einmal nachzulesen.