VTM 맞춤
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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를 반환합니다.
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
namespace UVC.Data.Http
|
||||
#nullable enable
|
||||
namespace UVC.Data.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// HttpResponseMask.Apply 메서드의 결과를 나타내는 클래스입니다.
|
||||
|
||||
@@ -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++)
|
||||
{
|
||||
|
||||
84
Assets/Scripts/UVC/Data/Mqtt/MqttDataPicker.cs
Normal file
84
Assets/Scripts/UVC/Data/Mqtt/MqttDataPicker.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Data/Mqtt/MqttDataPicker.cs.meta
Normal file
2
Assets/Scripts/UVC/Data/Mqtt/MqttDataPicker.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 200506528b00b484882fb5d7090a1d98
|
||||
@@ -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; // 마지막 메시지를 저장하여 나중에 사용할 수 있습니다.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
74
Assets/Scripts/UVC/Factory/Component/TestObject.cs
Normal file
74
Assets/Scripts/UVC/Factory/Component/TestObject.cs
Normal 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}");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Factory/Component/TestObject.cs.meta
Normal file
2
Assets/Scripts/UVC/Factory/Component/TestObject.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82997ed418c58a24a84cc69b11142bc0
|
||||
@@ -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
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace UVC.Tests.Data
|
||||
};
|
||||
|
||||
// Act
|
||||
_dataObject.UpdateDifferent(otherDataObject);
|
||||
_dataObject.UpdateDifferent(otherDataObject, true);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("Updated Test", _dataObject["name"]);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace UVC.UI.List.ComponentList
|
||||
|
||||
// 1. FactoryObjectManager에서 카테고리별로 그룹화된 객체 정보를 가져옵니다.
|
||||
var infos = FactoryObjectManager.Instance.GetFactoryObjectInfosByCategory();
|
||||
|
||||
|
||||
// 2. 각 카테고리에 대해 루프를 돕니다.
|
||||
foreach (var info in infos)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user