VTM 맞춤

This commit is contained in:
logonkhi
2025-09-23 20:40:31 +09:00
parent 1bc008159a
commit 4c87f14f14
139 changed files with 17542 additions and 1691 deletions

View File

@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using UnityEngine;
namespace UVC.Data.Core
{
@@ -262,7 +263,8 @@ namespace UVC.Data.Core
/// 변경된 키는 자동으로 추적됩니다.
/// </summary>
/// <param name="other">비교할 DataObject</param>
public void UpdateDifferent(IDataObject other)
/// <param name="updatedDataOnly">true로 설정하면, 다른 속성만 업데이트합니다. false로 설정하면, 모든 속성을 덮어씁니다.</param>
public void UpdateDifferent(IDataObject other, bool updatedDataOnly)
{
if (other == null) return;
if (!(other is DataArray otherArray)) return;
@@ -278,10 +280,13 @@ namespace UVC.Data.Core
var thisIds = new HashSet<string>(thisDict.Keys);
var otherIds = new HashSet<string>(otherDict.Keys);
// 제거된 항목 확인 (현재 배열에는 있지만 다른 배열에는 없는 항목)
foreach (var id in thisIds.Where(id => !otherIds.Contains(id)))
if (!updatedDataOnly)
{
removedList.Add(thisDict[id]);
// 제거된 항목 확인 (현재 배열에는 있지만 다른 배열에는 없는 항목)
foreach (var id in thisIds.Where(id => !otherIds.Contains(id)))
{
removedList.Add(thisDict[id]);
}
}
// 추가된 항목 확인 (다른 배열에는 있지만 현재 배열에는 없는 항목)
@@ -299,7 +304,7 @@ namespace UVC.Data.Core
// [성능 개선] ToString() 비교는 매우 비효율적입니다.
// DataObject.UpdateDifferent를 직접 호출하여 변경 사항을 적용하고,
// UpdatedCount를 통해 실제 변경 여부를 확인합니다.
thisItem.UpdateDifferent(otherItem);
thisItem.UpdateDifferent(otherItem, updatedDataOnly);
if (thisItem.UpdatedCount > 0)
{
modifiedList.Add(thisItem);

View File

@@ -270,6 +270,22 @@ namespace UVC.Data.Core
return this;
}
/// <summary>
/// JSON 문자열 표현에서 <see cref="DataObject"/> 인스턴스를 생성합니다.
/// </summary>
/// <remarks>이 메서드를 사용하여 JSON 문자열을 <see cref="DataObject"/>로 역직렬화합니다.
/// 입력 문자열이 예상 객체 구조의 유효한 JSON 표현인지 확인합니다.</remarks>
/// <param name="jsonString"><see cref="DataObject"/>로 파싱할 JSON 문자열입니다. null이거나 비어 있으면 안 됩니다.</param>
/// <returns>JSON 문자열의 데이터로 채워진 새 <see cref="DataObject"/> 인스턴스를 반환합니다. <paramref name="jsonString"/>이 null이거나 비어 있으면 현재 인스턴스를 반환합니다.</returns>
public DataObject FromJsonString(string jsonString)
{
if (!string.IsNullOrEmpty(jsonString))
{
return FromJObject(JObject.Parse(jsonString));
}
return this;
}
/// <summary>
/// JToken을 적절한 C# 객체 타입으로 변환하는 헬퍼 메서드입니다.
/// </summary>
@@ -789,7 +805,8 @@ namespace UVC.Data.Core
/// 변경된 키는 자동으로 추적됩니다.
/// </summary>
/// <param name="other">비교할 DataObject</param>
public void UpdateDifferent(IDataObject other)
/// <param name="updatedDataOnly">true인 경우, 변경된 속성만 업데이트합니다. false인 경우, 모든 속성을 동기화합니다.</param>
public void UpdateDifferent(IDataObject other, bool updatedDataOnly)
{
if (other == null || other is not DataObject otherDataObject) return;
@@ -822,6 +839,9 @@ namespace UVC.Data.Core
changedProperies.Add(keyValue.Key);
}
}
if (updatedDataOnly) return;
// 현재 객체에만 있는 속성은 제거합니다.
var keysToRemove = this.Keys.Except(otherDataObject.Keys).ToList();
foreach (var key in keysToRemove)
@@ -829,6 +849,7 @@ namespace UVC.Data.Core
this.Remove(key);
changedProperies.Remove(key);
}
}
/// <summary>

View File

@@ -21,7 +21,8 @@ namespace UVC.Data.Core
/// 변경된 키는 자동으로 추적됩니다.
/// </summary>
/// <param name="other">비교할 DataObject</param>
public void UpdateDifferent(IDataObject other);
/// <param name="updatedDataOnly">true로 설정하면, 다른 속성만 업데이트합니다. false로 설정하면, 모든 속성을 덮어씁니다.</param>
public void UpdateDifferent(IDataObject other, bool updatedDataOnly);
/// <summary>
/// 업데이트된 속성만 포함하는 새로운 DataObject를 반환합니다.

View File

@@ -60,7 +60,7 @@ namespace UVC.Data
private HttpDataFetcher httpFetcher = new HttpDataFetcher();
public HttpDataFetcher HttpFetcher => httpFetcher;
private MqttDataReceiver mqttReceiver = new MqttDataReceiver();
private MqttDataReceiver mqttReceiver = new MqttDataReceiver(new MqttDataPicker("data", true));
public MqttDataReceiver MqttReceiver => mqttReceiver;
@@ -96,7 +96,7 @@ namespace UVC.Data
else
{
IDataObject obj = dataObjects[key];
obj.UpdateDifferent(dataObject);
obj.UpdateDifferent(dataObject, updatedDataOnly);
IDataObject newDataObject;
if (updatedDataOnly)
{

View File

@@ -1,4 +1,5 @@
namespace UVC.Data.Http
#nullable enable
namespace UVC.Data.Http
{
/// <summary>
/// HttpResponseMask.Apply 메서드의 결과를 나타내는 클래스입니다.

View File

@@ -1,4 +1,4 @@
#nullable enable
#nullable enable
using NUnit.Framework;
using System;
using System.Collections.Generic;
@@ -88,6 +88,23 @@ namespace UVC.Data.Mqtt
}
}
/// <summary>
/// 토픽과 JSON 페이로드를 포함하는 MQTT 통신용 데이터 패킷을 나타냅니다.
/// </summary>
/// <remarks> <see cref="MqttDataPacket"/> 클래스는 MQTT 메시지의 토픽과 페이로드를 캡슐화하는 데 사용됩니다.
/// 타임스탬프 및 전파 상태와 같은 메타데이터도 함께 캡슐화합니다. <paramref
/// name="topic"/> 및 <paramref name="json"/> 매개변수는 올바른 사용을 위해 유효한 문자열이어야 합니다.
///</remarks>
/// <param name="topic">MQTT 메시지와 연결된 토픽입니다. null이거나 비어 있을 수 없습니다.</param>
/// <param name="json">MQTT 메시지의 JSON 페이로드입니다. null이거나 비어 있을 수 없습니다.</param>
public MqttDataPacket(string topic, string json)
{
this.Topic = topic;
this.Payload = json;
this.IsPropagated = false;
this.Timestamp = DateTime.UtcNow;
}
/// <summary>
/// DataObject 객체로부터 MqttDataPacket 인스턴스를 설정합니다.
/// TIMESTAMP 필드를 파싱하여 타임스탬프로 사용합니다.
@@ -113,6 +130,22 @@ namespace UVC.Data.Mqtt
return this;
}
/// <summary>
/// DataObject 객체로부터 MqttDataPacket 인스턴스를 설정합니다.
/// TIMESTAMP 필드를 파싱하여 타임스탬프로 사용합니다.
/// </summary>
/// <param name="topic">메시지가 수신된 토픽</param>
/// <param name="json">MQTT 메시지의 JSON 페이로드입니다. null이거나 비어 있을 수 없습니다.</param>
public MqttDataPacket FromJson(string topic, string json)
{
this.Topic = topic;
this.Payload = json;
this.IsPropagated = false;
this.Timestamp = DateTime.UtcNow;
return this;
}
/// <summary>
/// 객체를 초기 상태로 재설정합니다.
/// </summary>
@@ -148,6 +181,13 @@ namespace UVC.Data.Mqtt
public static string ToJsonFromList(List<MqttDataPacket> packets)
{
if (packets == null || packets.Count == 0) return "[]";
bool isObject = packets.Count == 1 && packets[0].Payload.Trim().StartsWith("{");
if (isObject)
{
return packets[0].Payload;
}
var jsonArray = new System.Text.StringBuilder("[");
for (int i = 0; i < packets.Count; i++)
{

View File

@@ -0,0 +1,84 @@
#nullable enable
using MessagePack;
using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.VisualScripting;
using UnityEngine;
using UVC.Json;
namespace UVC.Data.Mqtt
{
/// <summary>
/// 미리 정의된 키를 기반으로 JSON 문자열에서 특정 값을 추출합니다.
/// </summary>
/// <remarks>이 클래스는 JSON 문자열을 파싱하고 지정된 키와 연관된 값을 검색하도록 설계되었습니다.
/// 키는 클래스 인스턴스화 과정에서 제공됩니다. JSON 문자열에서 키를 찾을 수 없거나 JSON 문자열이 유효하지 않은 경우,
/// 메서드는 <see langword="null"/>을 반환합니다.</remarks>
public class MqttDataPicker
{
private bool enableMessagePack;
public bool EnableMessagePack { get { return enableMessagePack; } }
private string DataKeyName;
/// <summary>
/// 지정된 데이터 키 이름으로 <see cref="MqttDataPicker"/> 클래스의 새 인스턴스를 초기화합니다.
/// </summary>
/// <param name="dataKeyName">MQTT 메시지에서 데이터를 식별하는 데 사용할 데이터 키의 이름입니다. 이 값은 null이거나
/// 비어 있을 수 없습니다.</param>
/// <param name="enableMessagePack">MessaePack을 사용할 것인지 여부</param>
public MqttDataPicker(string dataKeyName, bool enableMessagePack = false)
{
DataKeyName = dataKeyName;
this.enableMessagePack = enableMessagePack;
}
/// <summary>
/// JSON 문자열에서 특정 키와 연관된 값을 추출합니다.
/// </summary>
/// <remarks>이 메서드는 제공된 JSON 문자열을 구문 분석하여 값을 검색하려고 시도합니다
/// <see cref="DataKeyName"/>로 정의된 키에 해당합니다. JSON이 형식이 올바르지 않거나 해당 키를 포함하지 않는 경우
/// 지정된 키에 대해 메서드는 <see langword="null"/>을 반환합니다.</remarks>
/// <param name="json">구문 분석할 JSON 형식의 문자열입니다. null이거나 비어서는 안 됩니다.</param>
/// <returns><see cref="DataKeyName">로 지정된 키와 연관된 값, 또는 키가 없는 경우 <see langword="null"/></returns>
/// 키를 찾을 수 없거나 입력이 유효하지 않습니다.</returns>
public string? PickData(string json)
{
//Newton JSON에서 변경 된 형태라, [123, 123] 형태의 messagePack 데이터가 [value:123, value:123] 형태로 변경
string jsonString = json;
try
{
if (enableMessagePack)
{
var msgPackObj = JsonHelper.FromJson<Dictionary<string, object>>(json);
if (msgPackObj.ContainsKey("type") && msgPackObj.TryGetValue("data", out object? dataObj) && dataObj != null)
{
string byteString = dataObj.ToString();
List<int> byteList = JsonHelper.FromJson<List<int>>(byteString);
byte[] bytes = byteList.Select(v => (byte)v).ToArray();
Dictionary<string, object>? obj = MessagePackSerializer.Deserialize<Dictionary<string, object>>(bytes);
if (obj != null)
{
jsonString = JsonHelper.ToJson(obj);
}
}
}
var jsonObj = JsonHelper.FromJson<Dictionary<string, object>>(jsonString);
if (jsonObj.TryGetValue(DataKeyName, out var value))
{
return value?.ToString();
}
}
catch (Exception ex)
{
Debug.LogError($"MqttDataPicker: PickData exception: {ex.Message}. {jsonString}");
}
return null;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 200506528b00b484882fb5d7090a1d98

View File

@@ -105,14 +105,18 @@ namespace UVC.Data.Mqtt
//mqtt 연결 후 topic 별 첫 번째 메시지를 수신 했는지 여부를 저장하는 딕셔너리
private ConcurrentDictionary<string, bool> firstMessageReceived = new ConcurrentDictionary<string, bool>();
private MqttDataPicker? defaultDataPicker;
/// <summary>
/// MqttDataReceiver 인스턴스를 생성합니다.
/// </summary>
public MqttDataReceiver()
public MqttDataReceiver(MqttDataPicker? dataPicker = null)
{
mqttWorker = new MqttWorker();
bool enableMessagePack = false;
if (dataPicker != null) enableMessagePack = dataPicker.EnableMessagePack;
mqttWorker = new MqttWorker(enableMessagePack);
configList = new ConcurrentDictionary<string, MqttSubscriptionConfig>();
defaultDataPicker = dataPicker;
}
/// <summary>
@@ -193,8 +197,10 @@ namespace UVC.Data.Mqtt
/// </summary>
/// <remarks>이 메서드는 구독된 MQTT 토픽에서 메시지가 수신될 때 호출됩니다.
/// 이 메서드를 호출하기 전에 <paramref name="packets"/> 매개변수가 null이 아닌지 확인하십시오.</remarks>
/// <param name="topic">수신된 메시지의 토픽입니다. null일 수 없습니다.</param>
/// <param name="packets">처리할 <see cref="MqttDataPacket"/> 객체 목록입니다. null일 수 없습니다.</param>
private void OnTopicPacketMessage(string topic, List<MqttDataPacket> packets)
/// <param name="isObject">수신된 메시지가 List에 한개의 객체인지 여부를 나타내는 부울 값입니다.</param>
private void OnTopicPacketMessage(string topic, List<MqttDataPacket> packets, bool isObject)
{
OnTopicMessage(topic, MqttDataPacket.ToJsonFromList(packets));
}
@@ -236,6 +242,12 @@ namespace UVC.Data.Mqtt
// 토픽이 infoList와 readyHandlerList에 존재하고, 준비 상태가 true인 경우에만 처리합니다.
if (configList.TryGetValue(topic, out var config))
{
if (defaultDataPicker != null)
{
string? dataMessage = defaultDataPicker.PickData(message);
if(dataMessage != null) message = dataMessage;
}
bool isFirstMessage = false;
// 첫 번째 메시지 수신 여부를 확인하고, 아직 수신하지 않았다면 처리합니다.
if (!firstMessageReceived.ContainsKey(topic) || !firstMessageReceived[topic])
@@ -244,8 +256,6 @@ namespace UVC.Data.Mqtt
isFirstMessage = true; // 첫 번째 메시지로 처리합니다.
}
IDataObject? mappedObject = null;
message = message.Trim();
LastMessage = message; // 마지막 메시지를 저장하여 나중에 사용할 수 있습니다.

View File

@@ -141,7 +141,7 @@ namespace UVC.Data.Mqtt
/// .SetHandler(data => ProcessSensorData(data));
/// </code>
/// </example>
public MqttSubscriptionConfig setDataMapper(DataMapper dataMapper)
public MqttSubscriptionConfig SetDataMapper(DataMapper dataMapper)
{
_dataMapper = dataMapper;
return this;
@@ -185,7 +185,7 @@ namespace UVC.Data.Mqtt
/// });
/// </code>
/// </example>
public MqttSubscriptionConfig setValidator(DataValidator validator)
public MqttSubscriptionConfig SetValidator(DataValidator validator)
{
this._validator = validator;
return this;

View File

@@ -44,7 +44,7 @@ namespace UVC.Data.Mqtt
private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private readonly Dictionary<string, List<MqttDataPacket>> topicBuffers = new Dictionary<string, List<MqttDataPacket>>();
private readonly Dictionary<string, Action<string, List<MqttDataPacket>>> listeners = new Dictionary<string, Action<string, List<MqttDataPacket>>>();
private readonly Dictionary<string, Action<string, List<MqttDataPacket>, bool>> listeners = new Dictionary<string, Action<string, List<MqttDataPacket>, bool>>();
private readonly object bufferAndListenerLock = new object();
/// <summary>
@@ -61,10 +61,15 @@ namespace UVC.Data.Mqtt
private string domain = "localhost";
private int port = 1883;
private bool enableMessagePack = false;
/// <summary>
/// MqttWorker의 생성자입니다.
/// </summary>
public MqttWorker() { }
public MqttWorker(bool enableMessagePack = false)
{
this.enableMessagePack = enableMessagePack;
}
/// <summary>
/// 현재 구성의 도메인과 포트를 설정합니다.
@@ -136,7 +141,7 @@ namespace UVC.Data.Mqtt
/// 중요: 여기서 등록된 리스너(콜백)는 백그라운드 스레드에서 호출됩니다.
/// Unity API(GameObject, Transform 등)에 직접 접근하면 안 됩니다.
/// </summary>
public void AddListener(string topic, Action<string, List<MqttDataPacket>> listener)
public void AddListener(string topic, Action<string, List<MqttDataPacket>, bool> listener)
{
lock (bufferAndListenerLock)
{
@@ -160,7 +165,7 @@ namespace UVC.Data.Mqtt
///</remarks>
/// <param name="topic">리스너를 제거할 토픽입니다. null이거나 비어 있을 수 없습니다.</param>
/// <param name="listener">제거할 리스너로, <see cref="Action{string, List{MqttDataPacket}}"/>으로 표현됩니다. null일 수 없습니다.</param>
public void RemoveListener(string topic, Action<string, List<MqttDataPacket>> listener)
public void RemoveListener(string topic, Action<string, List<MqttDataPacket>, bool> listener)
{
lock (bufferAndListenerLock)
{
@@ -268,13 +273,20 @@ namespace UVC.Data.Mqtt
{
// 버퍼링 모드: 메시지를 큐에 넣어 워커 스레드로 전달합니다.
try
{
var dataArray = DataArrayPool.Get().FromJsonString(message);
foreach (var dataObject in dataArray)
{
IncomingMessageQueue.Enqueue(MqttDataPacketPool.Get().FromDataObject(topic, dataObject));
{
if (message.Trim().StartsWith("{"))
{
IncomingMessageQueue.Enqueue(MqttDataPacketPool.Get().FromJson(topic, message));
}
else if (message.Trim().StartsWith("["))
{
var dataArray = DataArrayPool.Get().FromJsonString(message);
foreach (var dataObject in dataArray)
{
IncomingMessageQueue.Enqueue(MqttDataPacketPool.Get().FromDataObject(topic, dataObject));
}
dataArray.ReturnToPool();
}
dataArray.ReturnToPool();
}
catch (Exception ex)
{
@@ -290,16 +302,27 @@ namespace UVC.Data.Mqtt
{
try
{
var list = new List<MqttDataPacket>();
var dataArray = DataArrayPool.Get().FromJsonString(message);
foreach (var dataObject in dataArray)
if (message.Trim().StartsWith("{"))
{
list.Add(new MqttDataPacket(topic, dataObject));
var list = new List<MqttDataPacket>();
list.Add(new MqttDataPacket(topic, message));
// 직접 전파 모드에서도 타임스탬프 순으로 정렬
list.Sort((p1, p2) => p1.Timestamp.CompareTo(p2.Timestamp));
listeners[topic].Invoke(topic, list, true);
}
else if (message.Trim().StartsWith("["))
{
var list = new List<MqttDataPacket>();
var dataArray = DataArrayPool.Get().FromJsonString(message);
foreach (var dataObject in dataArray)
{
list.Add(new MqttDataPacket(topic, dataObject));
}
// 직접 전파 모드에서도 타임스탬프 순으로 정렬
list.Sort((p1, p2) => p1.Timestamp.CompareTo(p2.Timestamp));
listeners[topic].Invoke(topic, list, false);
dataArray.ReturnToPool();
}
// 직접 전파 모드에서도 타임스탬프 순으로 정렬
list.Sort((p1, p2) => p1.Timestamp.CompareTo(p2.Timestamp));
listeners[topic].Invoke(topic, list);
dataArray.ReturnToPool();
}
catch (Exception ex)
{
@@ -365,8 +388,8 @@ namespace UVC.Data.Mqtt
//타임스탬프 순으로 정렬
newPackets.Sort((p1, p2) => p1.Timestamp.CompareTo(p2.Timestamp));
// 중요: 이 콜백은 Worker 스레드에서 직접 호출됩니다.
listeners[topic]?.Invoke(topic, newPackets);
bool isObject = newPackets.Count == 1 && newPackets[0].Payload.Trim().StartsWith("{");
listeners[topic]?.Invoke(topic, newPackets, isObject);
}
catch (Exception ex)
{

View File

@@ -1,6 +1,7 @@
#nullable enable
using SampleProject;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
@@ -96,9 +97,41 @@ namespace UVC.Factory.Alarm
// - 유효한 데이터가 수신되면 OnUpdateData 메서드를 호출하여 처리합니다.
var pipelineInfo = new MqttSubscriptionConfig("ALARM");
// 데이터 마스크(DataMask) 정의:
// 수신할 데이터의 구조를 미리 정의합니다. 여기서 정의된 키(Key)들을 기준으로 데이터를 파싱합니다.
var alarmDataMask = new DataMask();
alarmDataMask.ObjectName = "ALARM"; // Alarm 객체의 이름을 설정합니다.
alarmDataMask.ObjectIdKey = "ID"; // Alarm의 고유 식별자로 사용할 키를 설정합니다.
//alarmDataMask["ID"] = "";
//alarmDataMask["ALARM_TYPE"] = "";
//alarmDataMask["LEVEL"] = "";
//alarmDataMask["LOGISTIC"] = "";
//alarmDataMask["STATE"] = "";
//alarmDataMask["MESSAGE"] = "";
//alarmDataMask["CODE"] = "";
//alarmDataMask["ICON"] = "";
//alarmDataMask["MACHINENAME"] = "";
//alarmDataMask["SHOPNAME"] = "";
//alarmDataMask["TRANSPORT_EQP_NAME"] = "";
//alarmDataMask["TRANSPORT_UNIT_NAME"] = "";
//alarmDataMask["TRANSPORT_EQP_ID"] = "";
//alarmDataMask["TRANSPORT_UNIT_ID"] = "";
//alarmDataMask["CLEAR_TIME"] = DateTime.Now;
//alarmDataMask["SET_TIME"] = DateTime.Now;
//alarmDataMask["UPDATE_TIME"] = DateTime.Now;
//alarmDataMask["TIMESTAMP"] = DateTime.Now;
// 데이터 유효성 검사기(DataValidator) 설정:
// 수신된 데이터가 유효한지 검사하는 규칙을 추가합니다.
// 여기서는 "MACHINENAME" 필드가 null이 아닌지 확인합니다.
DataValidator alarmValidator = new DataValidator();
alarmValidator.AddValidator("MACHINENAME", value => value != null);
DataMapper mapper = new DataMapper(alarmDataMask);
var dataMapperValidator = DataMapperValidator.Get("ALARM");
if (dataMapperValidator?.DataMapper != null) pipelineInfo.setDataMapper(dataMapperValidator.DataMapper);
if (dataMapperValidator?.Validator != null) pipelineInfo.setValidator(dataMapperValidator.Validator);
pipelineInfo.SetDataMapper(mapper);
pipelineInfo.SetValidator(alarmValidator);
// 4. 생성한 파이프라인을 전역 MQTT 파이프라인에 추가하여 데이터 수신을 시작합니다.
DataRepository.Instance.MqttReceiver.Add(pipelineInfo);

View File

@@ -112,10 +112,32 @@ namespace UVC.Factory.Component
//playback에서도 데이터를 업데이트 하기에 DataRepository에 핸들러를 추가합니다.
DataRepository.Instance.AddDataUpdateHandler("AGV", OnUpdateData);
var agvDataMask = new DataMask();
agvDataMask.ObjectName = "AGV"; // AGV 객체의 이름을 설정합니다.
agvDataMask.ObjectIdKey = "VHL_NAME"; // AGV의 고유 식별자로 사용할 키를 설정합니다.
//agvDataMask["VHL_NAME"] = "";
//agvDataMask["AGV_IDX"] = "";
//agvDataMask["B_INSTALL"] = "";
//agvDataMask["NODE_ID"] = "";
//agvDataMask["REAL_ID"] = "";
//agvDataMask["VHL_STATE"] = "";
//agvDataMask["BAY_LIST"] = "";
//agvDataMask["X"] = 0;
//agvDataMask["Y"] = 0;
//agvDataMask["DEGREE"] = 0.0;
//agvDataMask["MODE"] = "";
//agvDataMask["BATT"] = "";
//agvDataMask["SUB_GOAL"] = "";
//agvDataMask["FINAL_GOAL"] = "";
//agvDataMask["JOB_ID"] = "";
//agvDataMask["CARRIER_ID"] = "";
//agvDataMask["TIMESTAMP"] = DateTime.Now;
DataMapper mapper = new DataMapper(agvDataMask);
var mqttConfig = new MqttSubscriptionConfig("AGV");
var dataMapperValidator = DataMapperValidator.Get("AGV");
if (dataMapperValidator?.DataMapper != null) mqttConfig.setDataMapper(dataMapperValidator.DataMapper);
if (dataMapperValidator?.Validator != null) mqttConfig.setValidator(dataMapperValidator.Validator);
mqttConfig.SetDataMapper(mapper);
// 생성한 파이프라인 정보를 전역 MQTT 파이프라인에 추가합니다.
DataRepository.Instance.MqttReceiver.Add(mqttConfig);

View File

@@ -244,9 +244,9 @@ namespace UVC.Factory.Component
/// category 이름을 key, FactoryObjectInfo 리스트가 value인 딕셔너리를 반환합니다.
/// </summary>
/// <returns></returns>
public Dictionary<string, List<FactoryObjectInfo>> GetFactoryObjectInfosByCategory()
public SortedDictionary<string, List<FactoryObjectInfo>> GetFactoryObjectInfosByCategory()
{
Dictionary<string, List<FactoryObjectInfo>> categoryDict = new Dictionary<string, List<FactoryObjectInfo>>();
SortedDictionary<string, List<FactoryObjectInfo>> categoryDict = new SortedDictionary<string, List<FactoryObjectInfo>>();
foreach (var kvp in FactoryObjects)
{
var info = kvp.Key;
@@ -256,6 +256,14 @@ namespace UVC.Factory.Component
}
categoryDict[info.Category].Add(info);
}
//key 순 정렬
//name 순 정렬
foreach (var key in categoryDict.Keys)
{
categoryDict[key].Sort((a, b) => a.Name.CompareTo(b.Name));
}
return categoryDict;
}

View File

@@ -30,7 +30,7 @@ namespace UVC.Factory.Component
DataRepository.Instance.AddDataUpdateHandler("PORT", OnUpdateData);
var mqttConfig = new MqttSubscriptionConfig("PORT");
mqttConfig.setDataMapper(new DataMapper(new DataMask
mqttConfig.SetDataMapper(new DataMapper(new DataMask
{
ObjectIdKey = "PORT_ID",
ObjectName = "PORTNAME"
@@ -63,7 +63,7 @@ namespace UVC.Factory.Component
return;
}
ports[name] = port;
Debug.Log($"포트 {name}가 추가되었습니다.");
//Debug.Log($"포트 {name}가 추가되었습니다.");
}
/// <summary>
@@ -85,7 +85,9 @@ namespace UVC.Factory.Component
var RemovedItems = arr.RemovedItems;
var ModifiedList = arr.ModifiedList;
Debug.Log($"PORTManager received data: count:{arr.Count}, Added={AddedItems.Count}, Removed={RemovedItems.Count}, Modified={ModifiedList.Count}");
var portsIds = new HashSet<string>(ports.Keys);
Debug.Log($"PORTManager received data: ports:{ports.Count} portsIds:{portsIds.Count} count:{arr.Count}, Added={AddedItems.Count}, Removed={RemovedItems.Count}, Modified={ModifiedList.Count}");
// 추가된 항목 처리
foreach (var item in AddedItems)
@@ -98,7 +100,8 @@ namespace UVC.Factory.Component
{
Port port = ports[portId];
port.Info = new FactoryObjectInfo(
item.GetString("PORTNAME"),
//item.GetString("PORTNAME"),
item.GetString("PORT_ID"),
item.GetString("PORT_ID"),
port.areaType.ToString(),
port.areaType.ToString(),

View File

@@ -0,0 +1,74 @@
using SampleProject;
using System;
using UnityEngine;
using UVC.Core;
using UVC.Data;
using UVC.Data.Core;
using UVC.Data.Mqtt;
namespace UVC.Factory.Component
{
public class TestObject: MonoBehaviour
{
void Start()
{
var agvDataMask = new DataMask();
agvDataMask.ObjectName = "AGV_RECIPE"; // AGV 객체의 이름을 설정합니다.
agvDataMask.ObjectIdKey = "VHL_NAME"; // AGV의 고유 식별자로 사용할 키를 설정합니다.
//agvDataMask["VHL_NAME"] = "";
//agvDataMask["AGV_IDX"] = "";
//agvDataMask["B_INSTALL"] = "";
//agvDataMask["NODE_ID"] = "";
//agvDataMask["REAL_ID"] = "";
//agvDataMask["VHL_STATE"] = "";
//agvDataMask["BAY_LIST"] = "";
//agvDataMask["X"] = 0;
//agvDataMask["Y"] = 0;
//agvDataMask["DEGREE"] = 0.0;
//agvDataMask["MODE"] = "";
//agvDataMask["BATT"] = "";
//agvDataMask["SUB_GOAL"] = "";
//agvDataMask["FINAL_GOAL"] = "";
//agvDataMask["JOB_ID"] = "";
//agvDataMask["CARRIER_ID"] = "";
//agvDataMask["TIMESTAMP"] = DateTime.Now;
DataMapper mapper = new DataMapper(agvDataMask);
var mqttConfig = new MqttSubscriptionConfig("AGV_RECIPE");
mqttConfig.SetDataMapper(mapper);
// 생성한 파이프라인 정보를 전역 MQTT 파이프라인에 추가합니다.
DataRepository.Instance.MqttReceiver.Add(mqttConfig);
SceneMain.Instance.Initialized += OnSceneInitialized;
}
/// <summary>
/// 씬이 완전히 초기화된 후 호출됩니다.
/// AGV 데이터를 수신하기 위한 MQTT 파이프라인을 설정합니다.
/// </summary>
private void OnSceneInitialized()
{
//playback에서도 데이터를 업데이트 하기에 DataRepository에 핸들러를 추가합니다.
DataRepository.Instance.AddDataUpdateHandler("AGV_RECIPE", OnUpdateData);
}
public void OnUpdateData(IDataObject data)
{
if (data == null) return;
DataArray? arr = data as DataArray;
if (arr == null || arr.Count == 0) return;
// 데이터 배열에서 추가, 제거, 수정된 항목 리스트를 가져옵니다.
var AddedItems = arr.AddedItems;
var RemovedItems = arr.RemovedItems;
var ModifiedList = arr.ModifiedList;
Debug.Log($"TestObject received data: count:{arr.Count}, Added={AddedItems.Count}, Removed={RemovedItems.Count}, Modified={ModifiedList.Count}");
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 82997ed418c58a24a84cc69b11142bc0

View File

@@ -115,7 +115,7 @@ namespace UVC.Tests.Data
var otherArray = new DataArray(new[] { item2Modified, item4New });
// Act
_dataArray.UpdateDifferent(otherArray);
_dataArray.UpdateDifferent(otherArray, true);
// Assert
// 제거된 항목(_item1) 확인
@@ -145,7 +145,7 @@ namespace UVC.Tests.Data
// Arrange
// UpdateDifferent를 호출하여 변경 내역을 생성합니다.
var otherArray = new DataArray(new[] { _item3 });
_dataArray.UpdateDifferent(otherArray);
_dataArray.UpdateDifferent(otherArray, true);
Assert.Greater(_dataArray.UpdatedCount, 0, "테스트 준비 단계에서 변경 내역이 있어야 합니다.");
// Act

View File

@@ -100,7 +100,7 @@ namespace UVC.Tests.Data
};
// Act
_dataObject.UpdateDifferent(otherDataObject);
_dataObject.UpdateDifferent(otherDataObject, true);
// Assert
Assert.AreEqual("Updated Test", _dataObject["name"]);

View File

@@ -283,7 +283,7 @@ namespace UVC.Tests.Data
bool updatedDataOnly = topic != "ALL";
var pipelineInfo = new MqttSubscriptionConfig(topic, updatedDataOnly)
.setDataMapper(new DataMapper(dataMasks[topic]))
.SetDataMapper(new DataMapper(dataMasks[topic]))
.SetHandler(handlers[topic].HandleData);
mqttReceiver.Add(pipelineInfo);
@@ -314,7 +314,7 @@ namespace UVC.Tests.Data
// Arrange
// AGV 토픽만 등록
var agvInfo = new MqttSubscriptionConfig("AGV", true)
.setDataMapper(new DataMapper(dataMasks["AGV"]))
.SetDataMapper(new DataMapper(dataMasks["AGV"]))
.SetHandler(handlers["AGV"].HandleData);
mqttReceiver.Add(agvInfo);
@@ -349,7 +349,7 @@ namespace UVC.Tests.Data
// UpdatedDataOnly가 true인 AGV 토픽 추가
var agvInfo = new MqttSubscriptionConfig("AGV", true)
.setDataMapper(new DataMapper(dataMasks["AGV"]))
.SetDataMapper(new DataMapper(dataMasks["AGV"]))
.SetHandler(handlers["AGV"].HandleData);
testPipeLine.Add(agvInfo);
@@ -381,7 +381,7 @@ namespace UVC.Tests.Data
{
bool updatedDataOnly = topic != "ALL";
var pipelineInfo = new MqttSubscriptionConfig(topic, updatedDataOnly)
.setDataMapper(new DataMapper(dataMasks[topic]))
.SetDataMapper(new DataMapper(dataMasks[topic]))
.SetHandler(handlers[topic].HandleData);
testPipeLine.Add(pipelineInfo);
@@ -409,7 +409,7 @@ namespace UVC.Tests.Data
var testPipeLine = new TestMqttDataReceiver();
var pipelineInfo = new MqttSubscriptionConfig("AGV", true)
.setDataMapper(new DataMapper(dataMasks["AGV"]))
.SetDataMapper(new DataMapper(dataMasks["AGV"]))
.SetHandler(handlers["AGV"].HandleData);
testPipeLine.Add(pipelineInfo);
@@ -433,7 +433,7 @@ namespace UVC.Tests.Data
var testPipeLine = new TestMqttDataReceiver();
var pipelineInfo = new MqttSubscriptionConfig("AGV", true)
.setDataMapper(new DataMapper(dataMasks["AGV"]))
.SetDataMapper(new DataMapper(dataMasks["AGV"]))
.SetHandler(handlers["AGV"].HandleData);
testPipeLine.Add(pipelineInfo);
@@ -452,7 +452,7 @@ namespace UVC.Tests.Data
var testPipeLine = new TestMqttDataReceiver();
var pipelineInfo = new MqttSubscriptionConfig("AGV", true)
.setDataMapper(new DataMapper(dataMasks["AGV"]))
.SetDataMapper(new DataMapper(dataMasks["AGV"]))
.SetHandler(handlers["AGV"].HandleData);
testPipeLine.Add(pipelineInfo);
@@ -483,7 +483,7 @@ namespace UVC.Tests.Data
// 4. UpdatedDataOnly=true로 토픽 등록
var pipelineInfo = new MqttSubscriptionConfig("AGV", true)
.setDataMapper(new DataMapper(dataMask))
.SetDataMapper(new DataMapper(dataMask))
.SetHandler(handler.HandleData);
pipeline.Add(pipelineInfo);
@@ -597,8 +597,8 @@ namespace UVC.Tests.Data
});
var pipelineInfo = new MqttSubscriptionConfig("test_topic")
.setDataMapper(dataMapper)
.setValidator(validator)
.SetDataMapper(dataMapper)
.SetValidator(validator)
.SetHandler(handler.HandleData);
testPipeLine.Add(pipelineInfo);
@@ -636,8 +636,8 @@ namespace UVC.Tests.Data
});
var pipelineInfo = new MqttSubscriptionConfig("test_topic")
.setDataMapper(dataMapper)
.setValidator(validator)
.SetDataMapper(dataMapper)
.SetValidator(validator)
.SetHandler(handler.HandleData);
testPipeLine.Add(pipelineInfo);
@@ -673,8 +673,8 @@ namespace UVC.Tests.Data
});
var pipelineInfo = new MqttSubscriptionConfig("test_topic")
.setDataMapper(dataMapper)
.setValidator(validator)
.SetDataMapper(dataMapper)
.SetValidator(validator)
.SetHandler(handler.HandleData);
testPipeLine.Add(pipelineInfo);

View File

@@ -105,7 +105,7 @@ namespace UVC.UI.List.ComponentList
// 1. FactoryObjectManager에서 카테고리별로 그룹화된 객체 정보를 가져옵니다.
var infos = FactoryObjectManager.Instance.GetFactoryObjectInfosByCategory();
// 2. 각 카테고리에 대해 루프를 돕니다.
foreach (var info in infos)
{