#nullable enable using SampleProject; using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UVC.Core; using UVC.Data; using UVC.Extention; using UVC.Factory.Component; using UVC.Util; namespace UVC.Factory.Alarm { public class AlarmManager : SingletonScene { [Tooltip("알람 UI 프리팹입니다. 이 프리팹은 알람 정보를 표시하는 UI 요소를 포함해야 합니다.")] [SerializeField] protected GameObject alarmUIPrefab; // 알람 UI 프리팹 (아래에서 설명) private Dictionary activeAlarmUIs = new Dictionary(); private List agvNames = new List(); private Dictionary alarmAgvNames = new Dictionary(); // FactoryDataManager에서 찾을 수 있도록 참조를 저장 private FactoryObjectManager? dataManager; /// /// AlarmManager의 초기화 메서드입니다. /// Awake 메서드에서 호출되며, MonoBehaviour가 생성될 때 한 번만 실행됩니다. /// protected override void Init() { SceneMain.Instance.Initialized += OnSceneInitialized; } private void OnSceneInitialized() { dataManager = FactoryObjectManager.Instance; //test code //알람 데이터가 AGV와 관련 없는것이 많아서, AGV 이름을 미리 정의합니다. for (int i = 1; i <= 115; i++) { //agvNames.Add($"HFF09CNA8{i.ToString("D3")}"); agvNames.Add($"HFF09CNA8061"); } } /// /// Alarm 데이터를 수신하기 위한 MQTT 파이프라인을 설정합니다. /// public void Run() { //데이터를 어떤 형식으로 받을지 정의합니다. 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"] = ""; dataMask["CLEAR_TIME"] = DateTime.Now; dataMask["SET_TIME"] = DateTime.Now; dataMask["UPDATE_TIME"] = DateTime.Now; dataMask["TIMESTAMP"] = DateTime.Now; // MQTT 파이프라인 정보를 생성합니다. // 'ALARM' 토픽을 구독하고, 받은 데이터는 위에서 정의한 dataMask로 매핑하며, // 데이터 유효성 검사를 위해 DataValidator를 설정합니다. // 데이터가 업데이트되면 OnUpdateData 메서드를 호출하여 처리합니다. DataValidator validator = new DataValidator(); validator.AddValidator("MACHINENAME", value => value != null); var pipelineInfo = new MQTTPipeLineInfo("ALARM") .setDataMapper(new DataMapper(dataMask)) .setValidator(validator) .setHandler(OnUpdateData); // 생성한 파이프라인 정보를 전역 MQTT 파이프라인에 추가합니다. AppMain.Instance.MQTTPipeLine.Add(pipelineInfo); } int agvIdx = 50; /// /// 데이터 수신 시 호출되는 공개 핸들러입니다. /// 수신된 ALARM 데이터 배열을 비동기적으로 처리하여 씬에 반영합니다. /// 추가, 제거, 수정된 ALARM 데이터를 각각 구분하여 처리합니다. /// /// 수신된 데이터 객체 (DataArray 형태) 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 = new List(arr.RemovedItems); var ModifiedList = arr.ModifiedList; Debug.Log($"AlarmManager OnUpdateData: Added={AddedItems.Count}, Removed={RemovedItems.Count}, Modified={ModifiedList.Count}"); // clear_time이 있는 항목만 제거 리스트에 추가합니다. foreach (var item in AddedItems.ToList()) { if (item.GetDateTime("CLEAR_TIME") != null) { if (RemovedItems.FindIndex((i) => i.Id == item.Id) == -1) RemovedItems.Add(item); } } foreach (var item in ModifiedList.ToList()) { if (item.GetDateTime("CLEAR_TIME") != null) { if (RemovedItems.FindIndex((i) => i.Id == item.Id) == -1) RemovedItems.Add(item); } } // 새로 추가된 ALARM 처리 foreach (var item in AddedItems.ToList()) { if (item.GetDateTime("CLEAR_TIME") == null && !item.Id.IsNullOrEmpty()) { item["TRANSPORT_EQP_ID"] = agvNames[agvIdx]; // AGV 이름을 TRANSPORT_EQP_ID에 설정 alarmAgvNames.Add(item.Id!, agvNames[agvIdx]); HandleNewAlarm(item); agvIdx++; if(agvIdx >= agvNames.Count) agvIdx = 0; // AGV 이름이 부족할 경우 순환 } } // 정보가 수정된 ALARM 처리 foreach (var item in ModifiedList.ToList()) { if (item.GetDateTime("CLEAR_TIME") == null && !item.Id.IsNullOrEmpty() && alarmAgvNames.ContainsKey(item.Id!)) { item["TRANSPORT_EQP_ID"] = alarmAgvNames[item.Id!]; // 기존 AGV 이름 유지 HandleModifyAlarm(item); } } // 제거된 ALARM 처리 foreach (var item in RemovedItems.ToList()) { if (!item.Id.IsNullOrEmpty() && alarmAgvNames.ContainsKey(item.Id!)) { item["TRANSPORT_EQP_ID"] = alarmAgvNames[item.Id!]; // 기존 AGV 이름 유지 HandleClearedAlarm(item); } } } /// /// AlarmManager가 파괴될 때 호출됩니다. /// MQTT 파이프라인에서 'ALARM' 핸들러를 제거하여 메모리 누수를 방지합니다. /// protected override void OnDestroy() { base.OnDestroy(); AppMain.Instance.MQTTPipeLine.Remove("ALARM"); } public void HandleNewAlarm(DataObject data) { if (data.Id == null) { Debug.LogError($"New Alarm Received No data. {data}"); return; } string? eqpId = data.GetString("TRANSPORT_EQP_ID"); if (eqpId == null) return; FactoryObject? targetObject = dataManager!.FindByName(eqpId); Debug.Log($"AlarmManager {targetObject==null} {data.Id}, {eqpId}"); if (targetObject == null) return; if (activeAlarmUIs.ContainsKey(eqpId)) { if(activeAlarmUIs[eqpId].ContainsAlarm(data)) { activeAlarmUIs[eqpId].UpdateAlarm(data); } else { activeAlarmUIs[eqpId].AddAlarm(data); } } else { // 없으면 새로 생성 GameObject newUIObject = Instantiate(alarmUIPrefab, transform); // 매니저 하위에 생성 AlarmUIController newUiController = newUIObject.GetComponent(); newUiController.Initialize(targetObject, data); activeAlarmUIs.Add(eqpId, newUiController); } } public void HandleModifyAlarm(DataObject data) { if (data.Id == null) { Debug.LogError($"Modify Alarm Received No data. {data}"); return; } string? eqpId = data.GetString("TRANSPORT_EQP_ID"); if (eqpId == null) return; // 이미 해당 설비에 알람 UI가 떠 있는지 확인 if (activeAlarmUIs.TryGetValue(eqpId, out AlarmUIController uiController)) { // 있으면 기존 UI에 알람 정보 업데이트 if (uiController.ContainsAlarm(data)) { uiController.UpdateAlarm(data); } else { // 없으면 새로 추가 uiController.AddAlarm(data); } } } public void HandleClearedAlarm(DataObject data) { if (data.Id.IsNullOrEmpty()) { Debug.LogError($"Clear Alarm Received No data. {data}"); return; } string? eqpId = data.GetString("TRANSPORT_EQP_ID"); if (eqpId == null) return; if (activeAlarmUIs.TryGetValue(eqpId, out AlarmUIController uiController)) { // 있으면 기존 UI에 알람 정보 업데이트 if (uiController.ContainsAlarm(data)) { uiController.RemoveAlarm(data); if (uiController.GetAlarmCount() == 0) { activeAlarmUIs.Remove(data.Id!); Destroy(uiController.gameObject); } } } } } }