Files
XRLib/Assets/Scripts/UVC/Factory/Alarm/AlarmManager.cs

324 lines
15 KiB
C#
Raw Normal View History

2025-06-27 17:50:23 +09:00
#nullable enable
using SampleProject;
using System;
2025-06-23 20:06:15 +09:00
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
2025-06-27 17:50:23 +09:00
using UVC.Core;
using UVC.Data;
using UVC.Extention;
2025-06-23 20:06:15 +09:00
using UVC.Factory.Component;
namespace UVC.Factory.Alarm
{
2025-07-08 19:17:32 +09:00
/// <summary>
/// 씬에 발생하는 모든 알람을 관리하는 싱글톤 클래스입니다.
/// MQTT로부터 알람 데이터를 수신하여, 해당하는 설비(FactoryObject)에 알람 UI를 생성, 업데이트, 제거하는 역할을 담당합니다.
/// </summary>
2025-06-27 17:50:23 +09:00
public class AlarmManager : SingletonScene<AlarmManager>
2025-06-23 20:06:15 +09:00
{
2025-06-27 17:50:23 +09:00
[Tooltip("알람 UI 프리팹입니다. 이 프리팹은 알람 정보를 표시하는 UI 요소를 포함해야 합니다.")]
2025-07-01 20:10:15 +09:00
[SerializeField]
protected GameObject alarmUIPrefab; // 알람 UI 프리팹 (아래에서 설명)
2025-06-23 20:06:15 +09:00
2025-07-08 19:17:32 +09:00
// 현재 활성화된 알람 UI들을 관리하는 딕셔너리입니다.
// Key: 설비 ID (TRANSPORT_EQP_ID), Value: 해당 설비에 연결된 AlarmIconManager 인스턴스
private Dictionary<string, AlarmIconManager> activeAlarmUIs = new Dictionary<string, AlarmIconManager>();
// 테스트용으로 사용할 AGV 이름 리스트입니다.
2025-07-01 20:10:15 +09:00
private List<string> agvNames = new List<string>();
2025-07-08 19:17:32 +09:00
// 어떤 알람(Id)이 어떤 AGV(TRANSPORT_EQP_ID)에 할당되었는지 추적하기 위한 딕셔너리입니다.
2025-07-01 20:10:15 +09:00
private Dictionary<string, string> alarmAgvNames = new Dictionary<string, string>();
2025-07-08 19:17:32 +09:00
// 씬에 있는 모든 FactoryObject를 관리하는 매니저의 참조입니다.
// 알람 데이터에 포함된 설비 ID를 이용해 실제 3D 객체를 찾기 위해 사용됩니다.
2025-06-27 17:50:23 +09:00
private FactoryObjectManager? dataManager;
2025-06-23 20:06:15 +09:00
2025-07-01 20:10:15 +09:00
2025-06-27 17:50:23 +09:00
/// <summary>
/// AlarmManager의 초기화 메서드입니다.
/// Awake 메서드에서 호출되며, MonoBehaviour가 생성될 때 한 번만 실행됩니다.
/// </summary>
protected override void Init()
2025-06-23 20:06:15 +09:00
{
2025-07-08 19:17:32 +09:00
// SceneMain의 초기화가 완료되었을 때 OnSceneInitialized 메서드를 호출하도록 이벤트에 등록합니다.
// 이를 통해 필요한 다른 매니저들이 준비된 후에 로직을 실행할 수 있습니다.
2025-06-27 17:50:23 +09:00
SceneMain.Instance.Initialized += OnSceneInitialized;
2025-06-23 20:06:15 +09:00
}
2025-07-08 19:17:32 +09:00
/// <summary>
/// 씬의 주요 구성요소들이 초기화된 후 호출되는 메서드입니다.
/// </summary>
2025-06-27 17:50:23 +09:00
private void OnSceneInitialized()
2025-06-23 20:06:15 +09:00
{
2025-07-08 19:17:32 +09:00
// FactoryObjectManager의 인스턴스를 가져와 나중에 사용할 수 있도록 저장합니다.
2025-06-23 20:06:15 +09:00
dataManager = FactoryObjectManager.Instance;
2025-07-01 20:10:15 +09:00
2025-07-08 19:17:32 +09:00
// 테스트 코드: 실제 데이터가 없을 경우를 대비하여 가상의 AGV 이름 목록을 생성합니다.
// 현재는 알람 데이터에 AGV 정보가 없는 경우가 많아, 임시로 AGV 이름을 할당하기 위해 사용됩니다.
2025-07-01 20:10:15 +09:00
for (int i = 1; i <= 115; i++)
{
2025-07-02 18:24:00 +09:00
//agvNames.Add($"HFF09CNA8{i.ToString("D3")}");
agvNames.Add($"HFF09CNA8061");
2025-07-01 20:10:15 +09:00
}
2025-06-23 20:06:15 +09:00
}
2025-06-27 17:50:23 +09:00
/// <summary>
2025-07-08 19:17:32 +09:00
/// MQTT로부터 알람 데이터를 수신하기 위한 파이프라인을 설정하고 실행합니다.
/// 이 메서드는 외부에서 호출되어야 알람 데이터 수신이 시작됩니다.
2025-06-27 17:50:23 +09:00
/// </summary>
public void Run()
2025-06-23 20:06:15 +09:00
{
2025-07-08 19:17:32 +09:00
// 1. 데이터 마스크(DataMask) 정의:
// 수신할 데이터의 구조를 미리 정의합니다. 여기서 정의된 키(Key)들을 기준으로 데이터를 파싱합니다.
2025-06-27 17:50:23 +09:00
var dataMask = new DataMask();
dataMask.ObjectName = "Alarm"; // Alarm 객체의 이름을 설정합니다.
dataMask.ObjectIdKey = "ID"; // Alarm의 고유 식별자로 사용할 키를 설정합니다.
dataMask["ID"] = "";
dataMask["ALARM_TYPE"] = "";
dataMask["LEVEL"] = "";
dataMask["LOGISTIC"] = "";
dataMask["STATE"] = "";
dataMask["MESSAGE"] = "";
dataMask["CODE"] = "";
dataMask["ICON"] = "";
dataMask["MACHINENAME"] = "";
dataMask["SHOPNAME"] = "";
dataMask["TRANSPORT_EQP_NAME"] = "";
dataMask["TRANSPORT_UNIT_NAME"] = "";
dataMask["TRANSPORT_EQP_ID"] = "";
dataMask["TRANSPORT_UNIT_ID"] = "";
2025-07-01 20:10:15 +09:00
dataMask["CLEAR_TIME"] = DateTime.Now;
2025-06-27 17:50:23 +09:00
dataMask["SET_TIME"] = DateTime.Now;
dataMask["UPDATE_TIME"] = DateTime.Now;
dataMask["TIMESTAMP"] = DateTime.Now;
2025-07-08 19:17:32 +09:00
// 2. 데이터 유효성 검사기(DataValidator) 설정:
// 수신된 데이터가 유효한지 검사하는 규칙을 추가합니다.
// 여기서는 "MACHINENAME" 필드가 null이 아닌지 확인합니다.
2025-06-27 17:50:23 +09:00
DataValidator validator = new DataValidator();
validator.AddValidator("MACHINENAME", value => value != null);
2025-07-08 19:17:32 +09:00
// 3. MQTT 파이프라인 정보(MQTTPipeLineInfo) 생성:
// - "ALARM" 토픽을 구독합니다.
// - 위에서 정의한 dataMask를 사용해 수신된 JSON 데이터를 DataObject로 변환합니다.
// - validator를 사용해 데이터의 유효성을 검사합니다.
// - 유효한 데이터가 수신되면 OnUpdateData 메서드를 호출하여 처리합니다.
2025-06-27 17:50:23 +09:00
var pipelineInfo = new MQTTPipeLineInfo("ALARM")
.setDataMapper(new DataMapper(dataMask))
.setValidator(validator)
.setHandler(OnUpdateData);
2025-07-08 19:17:32 +09:00
// 4. 생성한 파이프라인을 전역 MQTT 파이프라인에 추가하여 데이터 수신을 시작합니다.
2025-06-27 17:50:23 +09:00
AppMain.Instance.MQTTPipeLine.Add(pipelineInfo);
}
2025-07-01 20:10:15 +09:00
2025-07-08 19:17:32 +09:00
// 테스트용으로, 새로 발생하는 알람에 AGV ID를 순차적으로 할당하기 위한 인덱스입니다.
2025-07-01 20:10:15 +09:00
int agvIdx = 50;
2025-06-27 17:50:23 +09:00
/// <summary>
2025-07-08 19:17:32 +09:00
/// MQTT 파이프라인으로부터 데이터가 업데이트될 때마다 호출되는 메인 핸들러입니다.
/// 수신된 알람 데이터 배열을 분석하여 추가, 수정, 제거된 알람을 각각 처리합니다.
2025-06-27 17:50:23 +09:00
/// </summary>
2025-07-08 19:17:32 +09:00
/// <param name="data">수신된 데이터 객체 (DataArray 형태여야 함)</param>
2025-06-27 17:50:23 +09:00
public void OnUpdateData(IDataObject? data)
{
if (data == null) return;
2025-07-08 19:17:32 +09:00
// 데이터를 DataArray 형태로 변환합니다. 아니거나 비어있으면 처리를 중단합니다.
2025-06-27 17:50:23 +09:00
DataArray? arr = data as DataArray;
2025-07-01 20:10:15 +09:00
if (arr == null || arr.Count == 0) return;
2025-06-27 17:50:23 +09:00
// 데이터 배열에서 추가, 제거, 수정된 항목 리스트를 가져옵니다.
var AddedItems = arr.AddedItems;
2025-07-01 20:10:15 +09:00
var RemovedItems = new List<DataObject>(arr.RemovedItems);
2025-06-27 17:50:23 +09:00
var ModifiedList = arr.ModifiedList;
2025-07-09 00:23:45 +09:00
//Debug.Log($"AlarmManager OnUpdateData: Added={AddedItems.Count}, Removed={RemovedItems.Count}, Modified={ModifiedList.Count}");
2025-06-27 17:50:23 +09:00
2025-07-08 19:17:32 +09:00
// 'CLEAR_TIME'이 설정된 항목은 '해제된 알람'으로 간주하고, 제거 리스트에 추가합니다.
// AddedItems나 ModifiedList에 포함되어 있더라도 CLEAR_TIME이 있으면 즉시 해제 처리하기 위함입니다.
2025-06-27 17:50:23 +09:00
foreach (var item in AddedItems.ToList())
2025-06-23 20:06:15 +09:00
{
2025-07-01 20:10:15 +09:00
if (item.GetDateTime("CLEAR_TIME") != null)
{
if (RemovedItems.FindIndex((i) => i.Id == item.Id) == -1) RemovedItems.Add(item);
}
2025-06-27 17:50:23 +09:00
}
2025-07-08 19:17:32 +09:00
2025-07-01 20:10:15 +09:00
foreach (var item in ModifiedList.ToList())
2025-06-27 17:50:23 +09:00
{
2025-07-01 20:10:15 +09:00
if (item.GetDateTime("CLEAR_TIME") != null)
{
if (RemovedItems.FindIndex((i) => i.Id == item.Id) == -1) RemovedItems.Add(item);
}
}
2025-07-08 19:17:32 +09:00
// [처리 1] 새로 추가된 알람 처리
2025-07-01 20:10:15 +09:00
foreach (var item in AddedItems.ToList())
{
2025-07-08 19:17:32 +09:00
// 알람이 해제되지 않았고(CLEAR_TIME == null), 고유 ID가 있는 경우에만 신규 알람으로 처리합니다.
2025-07-01 20:10:15 +09:00
if (item.GetDateTime("CLEAR_TIME") == null && !item.Id.IsNullOrEmpty())
{
2025-07-08 19:17:32 +09:00
// 테스트 코드: 수신된 알람 데이터에 임시로 AGV ID를 할당합니다.
item["TRANSPORT_EQP_ID"] = agvNames[agvIdx];
alarmAgvNames.Add(item.Id!, agvNames[agvIdx]); // 알람 ID와 할당된 AGV ID를 매핑하여 저장합니다.
HandleNewAlarm(item); // 신규 알람 처리 로직 호출
2025-07-01 20:10:15 +09:00
agvIdx++;
2025-07-08 19:17:32 +09:00
if (agvIdx >= agvNames.Count) agvIdx = 0; // AGV ID를 순환하며 사용합니다.
2025-07-01 20:10:15 +09:00
}
2025-06-27 17:50:23 +09:00
}
2025-07-08 19:17:32 +09:00
// [처리 2] 정보가 수정된 알람 처리
2025-06-27 17:50:23 +09:00
foreach (var item in ModifiedList.ToList())
{
2025-07-08 19:17:32 +09:00
// 해제되지 않았고, ID가 있으며, 이미 관리 중인 알람인 경우에만 수정 처리합니다.
2025-07-01 20:10:15 +09:00
if (item.GetDateTime("CLEAR_TIME") == null && !item.Id.IsNullOrEmpty() && alarmAgvNames.ContainsKey(item.Id!))
{
2025-07-08 19:17:32 +09:00
item["TRANSPORT_EQP_ID"] = alarmAgvNames[item.Id!]; // 기존에 할당된 AGV ID를 유지합니다.
HandleModifyAlarm(item); // 수정 알람 처리 로직 호출
2025-07-01 20:10:15 +09:00
}
2025-06-27 17:50:23 +09:00
}
2025-07-08 19:17:32 +09:00
// [처리 3] 제거(해제)된 알람 처리
2025-07-01 20:10:15 +09:00
foreach (var item in RemovedItems.ToList())
{
2025-07-08 19:17:32 +09:00
// ID가 있고, 관리 중인 알람인 경우에만 해제 처리합니다.
2025-07-01 20:10:15 +09:00
if (!item.Id.IsNullOrEmpty() && alarmAgvNames.ContainsKey(item.Id!))
{
2025-07-08 19:17:32 +09:00
item["TRANSPORT_EQP_ID"] = alarmAgvNames[item.Id!]; // 기존 AGV ID를 설정하여 어떤 UI를 제거할지 찾도록 합니다.
HandleClearedAlarm(item); // 해제 알람 처리 로직 호출
2025-07-01 20:10:15 +09:00
}
}
2025-06-27 17:50:23 +09:00
}
/// <summary>
/// AlarmManager가 파괴될 때 호출됩니다.
2025-07-08 19:17:32 +09:00
/// 등록했던 MQTT 파이프라인 핸들러를 제거하여 메모리 누수 및 원치 않는 동작을 방지합니다.
2025-06-27 17:50:23 +09:00
/// </summary>
protected override void OnDestroy()
{
base.OnDestroy();
2025-07-08 19:17:32 +09:00
// AppMain의 MQTTPipeLine에서 "ALARM" 토픽에 대한 핸들러를 제거합니다.
2025-06-27 17:50:23 +09:00
AppMain.Instance.MQTTPipeLine.Remove("ALARM");
}
2025-07-08 19:17:32 +09:00
/// <summary>
/// 새로운 알람 데이터를 받아 UI를 생성하고 관리 목록에 추가합니다.
/// </summary>
/// <param name="data">새로운 알람 정보</param>
2025-06-27 17:50:23 +09:00
public void HandleNewAlarm(DataObject data)
{
if (data.Id == null)
{
Debug.LogError($"New Alarm Received No data. {data}");
return;
2025-06-23 20:06:15 +09:00
}
2025-06-27 17:50:23 +09:00
2025-07-08 19:17:32 +09:00
// 알람 데이터에서 설비 ID(TRANSPORT_EQP_ID)를 가져옵니다.
2025-07-02 18:24:00 +09:00
string? eqpId = data.GetString("TRANSPORT_EQP_ID");
if (eqpId == null) return;
2025-07-08 19:17:32 +09:00
// 설비 ID를 이용해 씬에서 실제 FactoryObject를 찾습니다.
2025-07-02 18:24:00 +09:00
FactoryObject? targetObject = dataManager!.FindByName(eqpId);
2025-07-09 00:23:45 +09:00
//Debug.Log($"AlarmManager {targetObject == null} {data.Id}, {eqpId}");
2025-07-02 18:24:00 +09:00
if (targetObject == null) return;
2025-07-08 19:17:32 +09:00
// 해당 설비에 이미 활성화된 알람 UI가 있는지 확인합니다.
2025-07-02 18:24:00 +09:00
if (activeAlarmUIs.ContainsKey(eqpId))
{
2025-07-08 19:17:32 +09:00
// 이미 UI가 있다면, 해당 UI에 새로운 알람 정보를 추가하거나 업데이트합니다.
if (activeAlarmUIs[eqpId].ContainsAlarm(data))
2025-07-02 18:24:00 +09:00
{
activeAlarmUIs[eqpId].UpdateAlarm(data);
}
else
{
activeAlarmUIs[eqpId].AddAlarm(data);
}
}
else
2025-06-27 17:50:23 +09:00
{
2025-07-08 19:17:32 +09:00
// 활성화된 UI가 없다면 새로 생성합니다.
2025-06-27 17:50:23 +09:00
GameObject newUIObject = Instantiate(alarmUIPrefab, transform); // 매니저 하위에 생성
2025-07-08 19:17:32 +09:00
AlarmIconManager newUiController = newUIObject.GetComponent<AlarmIconManager>(); // AlarmManager 하위에 UI 생성
newUiController.Initialize(targetObject, data); // 찾은 3D 객체와 알람 데이터로 UI 초기화
2025-06-27 17:50:23 +09:00
2025-07-08 19:17:32 +09:00
// 새로 생성된 UI를 관리 목록에 추가합니다.
2025-07-02 18:24:00 +09:00
activeAlarmUIs.Add(eqpId, newUiController);
2025-06-27 17:50:23 +09:00
}
2025-07-08 19:17:32 +09:00
2025-06-27 17:50:23 +09:00
2025-06-23 20:06:15 +09:00
}
2025-07-08 19:17:32 +09:00
/// <summary>
/// 기존 알람의 정보가 변경되었을 때 UI를 업데이트합니다.
/// </summary>
/// <param name="data">수정된 알람 정보</param>
2025-06-27 17:50:23 +09:00
public void HandleModifyAlarm(DataObject data)
2025-06-23 20:06:15 +09:00
{
2025-06-27 17:50:23 +09:00
if (data.Id == null)
{
Debug.LogError($"Modify Alarm Received No data. {data}");
return;
}
2025-07-02 18:24:00 +09:00
string? eqpId = data.GetString("TRANSPORT_EQP_ID");
if (eqpId == null) return;
2025-07-08 19:17:32 +09:00
// 해당 설비에 연결된 알람 UI가 있는지 확인합니다.
if (activeAlarmUIs.TryGetValue(eqpId, out AlarmIconManager uiController))
2025-06-27 17:50:23 +09:00
{
2025-07-08 19:17:32 +09:00
// 이미 존재하는 알람 정보가 수정된 것이라면 업데이트합니다.
2025-07-02 18:24:00 +09:00
if (uiController.ContainsAlarm(data))
{
uiController.UpdateAlarm(data);
}
else
{
2025-07-08 19:17:32 +09:00
// 드물지만, 수정 목록에 왔으나 새로운 알람인 경우 추가합니다.
2025-07-02 18:24:00 +09:00
uiController.AddAlarm(data);
}
2025-06-27 17:50:23 +09:00
}
}
2025-07-08 19:17:32 +09:00
/// <summary>
/// 해제된 알람을 받아 해당하는 UI를 제거하거나 업데이트합니다.
/// </summary>
/// <param name="data">해제된 알람 정보</param>
2025-06-27 17:50:23 +09:00
public void HandleClearedAlarm(DataObject data)
{
if (data.Id.IsNullOrEmpty())
{
Debug.LogError($"Clear Alarm Received No data. {data}");
return;
}
2025-07-02 18:24:00 +09:00
string? eqpId = data.GetString("TRANSPORT_EQP_ID");
if (eqpId == null) return;
2025-07-08 19:17:32 +09:00
// 해당 설비에 연결된 알람 UI가 있는지 확인합니다.
if (activeAlarmUIs.TryGetValue(eqpId, out AlarmIconManager uiController))
2025-06-23 20:06:15 +09:00
{
2025-07-08 19:17:32 +09:00
// UI가 관리하는 알람 목록에 해당 알람이 있는지 확인합니다.
2025-07-02 18:24:00 +09:00
if (uiController.ContainsAlarm(data))
2025-06-23 20:06:15 +09:00
{
2025-07-08 19:17:32 +09:00
// 목록에서 알람을 제거합니다.
2025-07-02 18:24:00 +09:00
uiController.RemoveAlarm(data);
2025-07-08 19:17:32 +09:00
// 만약 해당 UI에 더 이상 표시할 알람이 없다면,
2025-07-02 18:24:00 +09:00
if (uiController.GetAlarmCount() == 0)
{
2025-07-08 19:17:32 +09:00
// 관리 목록에서 제거하고 씬에서 UI 게임 오브젝트를 파괴합니다.
2025-07-02 18:24:00 +09:00
activeAlarmUIs.Remove(data.Id!);
Destroy(uiController.gameObject);
}
2025-06-23 20:06:15 +09:00
}
}
}
}
}