763 lines
26 KiB
C#
763 lines
26 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using Cysharp.Threading.Tasks;
|
|
using UnityEngine;
|
|
using UVC.Json;
|
|
|
|
namespace UVC.Studio.Config
|
|
{
|
|
/// <summary>
|
|
/// 장비 라이브러리 데이터 관리 클래스
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para><b>[ 개요 ]</b></para>
|
|
/// <para>StreamingAssets 폴더의 Library JSON 파일들을 로드하여 장비 정보를 관리합니다.</para>
|
|
/// <para>지원 파일: LibraryStakerCrane.json, LibraryRackSingle.json, LibraryAGV.json</para>
|
|
///
|
|
/// <para><b>[ JSON 파일 구조 ]</b></para>
|
|
/// <code>
|
|
/// LibraryXXX.json
|
|
/// └── [equipmentType]: List<EquipmentItem>
|
|
/// └── EquipmentItem
|
|
/// ├── id - 장비 식별자 (예: "SingleFork")
|
|
/// ├── label - 표시 이름 (예: "Single Fork")
|
|
/// ├── gltf - glTF 파일 경로 (예: "staker_crane/SingleFork.glb")
|
|
/// ├── image - 썸네일 이미지 경로 (선택)
|
|
/// ├── propertiesInfo - 속성 정보 목록
|
|
/// │ └── PropertiesInfo
|
|
/// │ ├── section - 섹션 이름 (예: "Frame", "Fork")
|
|
/// │ ├── modelPart - 3D 모델 파트 이름 (예: "Fork_01")
|
|
/// │ ├── modelType - 모델 타입 (General, Stretch, Repeat)
|
|
/// │ └── properties - 속성 항목 목록
|
|
/// │ └── PropertyItem
|
|
/// │ ├── id - 속성 ID (선택)
|
|
/// │ ├── label - 표시 이름 (예: "너비")
|
|
/// │ ├── type - 데이터 타입 (Float, Int)
|
|
/// │ ├── value - 값 (문자열)
|
|
/// │ └── unit - 단위 (예: "cm", "cm/s")
|
|
/// └── statusInfo - 상태 정보
|
|
/// ├── network - 네트워크 상태 정의
|
|
/// └── equipment - 설비 상태 정의
|
|
/// └── StatusSection
|
|
/// ├── section - 섹션 이름
|
|
/// └── properties - 상태 항목 목록
|
|
/// └── StatusProperty
|
|
/// ├── label - 상태 값 (예: "0", "true")
|
|
/// ├── stat - 상태 이름 (예: "normal", "running")
|
|
/// └── value - 색상 코드 (예: "#228B22")
|
|
/// </code>
|
|
///
|
|
/// <para><b>[ 사용 예시 (비동기) ]</b></para>
|
|
/// <code>
|
|
/// // 비동기 로드 - 모든 라이브러리
|
|
/// var library = new Library();
|
|
/// await library.LoadAllAsync();
|
|
///
|
|
/// // 비동기 로드 - 개별 라이브러리
|
|
/// await library.LoadStakerCraneAsync();
|
|
/// await library.LoadAGVAsync();
|
|
///
|
|
/// // 병렬 로드
|
|
/// await library.LoadAllParallelAsync();
|
|
///
|
|
/// // 데이터 접근
|
|
/// foreach (var crane in library.StakerCraneData.stakerCrane)
|
|
/// {
|
|
/// Debug.Log($"ID: {crane.id}, Label: {crane.label}");
|
|
/// }
|
|
/// </code>
|
|
///
|
|
/// <para><b>[ 사용 예시 (동기) ]</b></para>
|
|
/// <code>
|
|
/// // 동기 로드
|
|
/// var library = new Library();
|
|
/// library.LoadAll();
|
|
///
|
|
/// // 개별 로드
|
|
/// library.LoadStakerCrane();
|
|
/// </code>
|
|
///
|
|
/// <para><b>[ 지원 장비 타입 ]</b></para>
|
|
/// <list type="bullet">
|
|
/// <item><description><b>StakerCrane:</b> 스태커 크레인 - 자동 창고 시스템의 입출고 장비</description></item>
|
|
/// <item><description><b>RackSingle:</b> 단일 랙 - 물품 보관 선반</description></item>
|
|
/// <item><description><b>AGV:</b> 무인 운반차 (Automated Guided Vehicle)</description></item>
|
|
/// </list>
|
|
///
|
|
/// <para><b>[ Injector 등록 및 사용 ]</b></para>
|
|
/// <para>AppContext에서 Library를 등록하고, 다른 컴포넌트에서 주입받아 사용합니다.</para>
|
|
///
|
|
/// <para><b>1. AppContext에서 등록 (비동기 - 병렬 로드)</b></para>
|
|
/// <code>
|
|
/// public class StudioAppContext : InjectorAppContext
|
|
/// {
|
|
/// protected override async UniTask RegisterServicesAsync()
|
|
/// {
|
|
/// // Library 생성 및 병렬 비동기 로드 (가장 빠름)
|
|
/// var library = new Library();
|
|
/// await library.LoadAllParallelAsync();
|
|
///
|
|
/// // Injector에 인스턴스 등록
|
|
/// Injector.RegisterInstance<Library>(library);
|
|
/// }
|
|
/// }
|
|
/// </code>
|
|
///
|
|
/// <para><b>2. AppContext에서 등록 (동기)</b></para>
|
|
/// <code>
|
|
/// public class StudioAppContext : InjectorAppContext
|
|
/// {
|
|
/// protected override void RegisterServices()
|
|
/// {
|
|
/// // Library 생성 및 동기 로드
|
|
/// var library = new Library();
|
|
/// library.LoadAll();
|
|
///
|
|
/// // Injector에 인스턴스 등록
|
|
/// Injector.RegisterInstance<Library>(library);
|
|
/// }
|
|
/// }
|
|
/// </code>
|
|
///
|
|
/// <para><b>3. 컴포넌트에서 주입받아 사용</b></para>
|
|
/// <code>
|
|
/// public class EquipmentSpawner : MonoBehaviour
|
|
/// {
|
|
/// [Inject] private Library _library;
|
|
///
|
|
/// private void Start()
|
|
/// {
|
|
/// // 스태커 크레인 데이터 접근
|
|
/// foreach (var crane in _library.StakerCraneData.stakerCrane)
|
|
/// {
|
|
/// Debug.Log($"ID: {crane.id}, GLTF: {crane.gltf}");
|
|
/// }
|
|
///
|
|
/// // AGV 데이터 접근
|
|
/// foreach (var agv in _library.AGVData.AGV)
|
|
/// {
|
|
/// Debug.Log($"AGV: {agv.label}");
|
|
/// }
|
|
/// }
|
|
///
|
|
/// public EquipmentItem GetEquipmentById(string id)
|
|
/// {
|
|
/// return _library.StakerCraneData.stakerCrane
|
|
/// .FirstOrDefault(e => e.id == id);
|
|
/// }
|
|
/// }
|
|
/// </code>
|
|
///
|
|
/// <para><b>4. 직접 접근 (Inject 없이)</b></para>
|
|
/// <code>
|
|
/// // InjectorAppContext를 통해 직접 접근
|
|
/// var library = InjectorAppContext.Instance.Get<Library>();
|
|
/// var craneList = library.StakerCraneData.stakerCrane;
|
|
/// </code>
|
|
///
|
|
/// <para><b>5. Setting과 Library 함께 등록</b></para>
|
|
/// <code>
|
|
/// public class StudioAppContext : InjectorAppContext
|
|
/// {
|
|
/// protected override async UniTask RegisterServicesAsync()
|
|
/// {
|
|
/// // Setting과 Library를 병렬로 로드
|
|
/// var setting = new Setting();
|
|
/// var library = new Library();
|
|
///
|
|
/// await UniTask.WhenAll(
|
|
/// setting.LoadAsync(),
|
|
/// library.LoadAllParallelAsync()
|
|
/// );
|
|
///
|
|
/// // 인스턴스 등록
|
|
/// Injector.RegisterInstance<Setting>(setting);
|
|
/// Injector.RegisterInstance<Library>(library);
|
|
/// }
|
|
/// }
|
|
/// </code>
|
|
/// </remarks>
|
|
public class Library
|
|
{
|
|
/// <summary>스태커 크레인 라이브러리 파일명</summary>
|
|
private const string StakerCraneFileName = "StakerCrane.json";
|
|
|
|
/// <summary>단일 랙 라이브러리 파일명</summary>
|
|
private const string RackFileName = "Rack.json";
|
|
|
|
/// <summary>AGV 라이브러리 파일명</summary>
|
|
private const string AGVFileName = "AGV.json";
|
|
|
|
/// <summary>스태커 크레인 데이터</summary>
|
|
public LibraryStakerCraneData StakerCraneData { get; private set; }
|
|
|
|
/// <summary>단일 랙 데이터</summary>
|
|
public LibraryRackSingleData RackSingleData { get; private set; }
|
|
|
|
/// <summary>AGV 데이터</summary>
|
|
public LibraryAGVData AGVData { get; private set; }
|
|
|
|
/// <summary>모든 라이브러리 로드 완료 여부</summary>
|
|
public bool IsLoaded { get; private set; }
|
|
|
|
/// <summary>
|
|
/// 라이브러리 파일 경로
|
|
/// </summary>
|
|
public string LibraryPath
|
|
{
|
|
get
|
|
{
|
|
if (!useAppDataPath)
|
|
{
|
|
return Path.Combine(Application.streamingAssetsPath, "Studio", "Library") + "/";
|
|
}
|
|
else
|
|
{
|
|
return Path.Combine(Application.persistentDataPath, "Library") + "/";
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool useAppDataPath = false;
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// 생성자 - 기본 데이터로 초기화합니다.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 비동기 로드를 사용하려면 생성 후 LoadAllAsync()를 호출하세요.
|
|
/// 동기 로드를 원하면 LoadAll()을 호출하세요.
|
|
/// </remarks>
|
|
public Library(bool useAppDataPath = false)
|
|
{
|
|
this.useAppDataPath = useAppDataPath;
|
|
StakerCraneData = new LibraryStakerCraneData();
|
|
RackSingleData = new LibraryRackSingleData();
|
|
AGVData = new LibraryAGVData();
|
|
IsLoaded = false;
|
|
}
|
|
|
|
#region Async Methods
|
|
|
|
/// <summary>
|
|
/// 모든 라이브러리 파일을 순차적으로 비동기 로드합니다.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">취소 토큰</param>
|
|
public async UniTask LoadAllAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
await LoadStakerCraneAsync(cancellationToken);
|
|
await LoadRackSingleAsync(cancellationToken);
|
|
await LoadAGVAsync(cancellationToken);
|
|
IsLoaded = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 모든 라이브러리 파일을 병렬로 비동기 로드합니다.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">취소 토큰</param>
|
|
/// <remarks>
|
|
/// 세 파일을 동시에 로드하여 전체 로딩 시간을 단축합니다.
|
|
/// </remarks>
|
|
public async UniTask LoadAllParallelAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
await UniTask.WhenAll(
|
|
LoadStakerCraneAsync(cancellationToken),
|
|
LoadRackSingleAsync(cancellationToken),
|
|
LoadAGVAsync(cancellationToken)
|
|
);
|
|
IsLoaded = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// LibraryStakerCrane.json 파일을 비동기로 로드합니다.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">취소 토큰</param>
|
|
/// <returns>로드 성공 여부</returns>
|
|
public async UniTask<bool> LoadStakerCraneAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
var result = await LoadJsonAsync<LibraryStakerCraneData>(StakerCraneFileName, cancellationToken);
|
|
StakerCraneData = result ?? new LibraryStakerCraneData();
|
|
return result != null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// LibraryRackSingle.json 파일을 비동기로 로드합니다.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">취소 토큰</param>
|
|
/// <returns>로드 성공 여부</returns>
|
|
public async UniTask<bool> LoadRackSingleAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
var result = await LoadJsonAsync<LibraryRackSingleData>(RackFileName, cancellationToken);
|
|
RackSingleData = result ?? new LibraryRackSingleData();
|
|
return result != null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// LibraryAGV.json 파일을 비동기로 로드합니다.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">취소 토큰</param>
|
|
/// <returns>로드 성공 여부</returns>
|
|
public async UniTask<bool> LoadAGVAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
var result = await LoadJsonAsync<LibraryAGVData>(AGVFileName, cancellationToken);
|
|
AGVData = result ?? new LibraryAGVData();
|
|
return result != null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// JSON 파일을 비동기로 로드하여 지정된 타입으로 역직렬화합니다.
|
|
/// </summary>
|
|
/// <typeparam name="T">역직렬화 대상 타입</typeparam>
|
|
/// <param name="fileName">로드할 파일명</param>
|
|
/// <param name="cancellationToken">취소 토큰</param>
|
|
/// <returns>역직렬화된 데이터, 실패 시 null</returns>
|
|
/// <remarks>
|
|
/// 파일 읽기와 JSON 파싱을 백그라운드 스레드에서 수행합니다.
|
|
/// </remarks>
|
|
private async UniTask<T> LoadJsonAsync<T>(string fileName, CancellationToken cancellationToken = default) where T : class
|
|
{
|
|
string path = Path.Combine(Application.streamingAssetsPath, "Studio", "Library", fileName);
|
|
if (useAppDataPath) path = Path.Combine(Application.persistentDataPath, "Library", fileName);
|
|
|
|
if (!File.Exists(path))
|
|
{
|
|
Debug.LogWarning($"[Library] File not found: {path}");
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
// 파일 읽기를 백그라운드 스레드에서 수행
|
|
string json = await UniTask.RunOnThreadPool(
|
|
() => File.ReadAllText(path),
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
// JSON 파싱을 백그라운드 스레드에서 수행
|
|
T data = await UniTask.RunOnThreadPool(
|
|
() => JsonHelper.FromJson<T>(json),
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
Debug.Log($"[Library] Loaded successfully: {fileName}");
|
|
return data;
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
Debug.Log($"[Library] Load cancelled: {fileName}");
|
|
throw;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"[Library] Failed to load {fileName}: {e.Message}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Sync Methods
|
|
|
|
/// <summary>
|
|
/// 모든 라이브러리 파일을 동기로 로드합니다.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 메인 스레드를 블로킹하므로, 가능하면 LoadAllAsync() 또는 LoadAllParallelAsync() 사용을 권장합니다.
|
|
/// </remarks>
|
|
public void LoadAll()
|
|
{
|
|
LoadStakerCrane();
|
|
LoadRackSingle();
|
|
LoadAGV();
|
|
IsLoaded = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// LibraryStakerCrane.json 파일을 동기로 로드합니다.
|
|
/// </summary>
|
|
/// <returns>로드 성공 여부</returns>
|
|
public bool LoadStakerCrane()
|
|
{
|
|
var result = LoadJson<LibraryStakerCraneData>(StakerCraneFileName);
|
|
StakerCraneData = result ?? new LibraryStakerCraneData();
|
|
return result != null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// LibraryRackSingle.json 파일을 동기로 로드합니다.
|
|
/// </summary>
|
|
/// <returns>로드 성공 여부</returns>
|
|
public bool LoadRackSingle()
|
|
{
|
|
var result = LoadJson<LibraryRackSingleData>(RackFileName);
|
|
RackSingleData = result ?? new LibraryRackSingleData();
|
|
return result != null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// LibraryAGV.json 파일을 동기로 로드합니다.
|
|
/// </summary>
|
|
/// <returns>로드 성공 여부</returns>
|
|
public bool LoadAGV()
|
|
{
|
|
var result = LoadJson<LibraryAGVData>(AGVFileName);
|
|
AGVData = result ?? new LibraryAGVData();
|
|
return result != null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// JSON 파일을 동기로 로드하여 지정된 타입으로 역직렬화합니다.
|
|
/// </summary>
|
|
/// <typeparam name="T">역직렬화 대상 타입</typeparam>
|
|
/// <param name="fileName">로드할 파일명</param>
|
|
/// <returns>역직렬화된 데이터, 실패 시 null</returns>
|
|
private T LoadJson<T>(string fileName) where T : class
|
|
{
|
|
string path = Path.Combine(Application.streamingAssetsPath, fileName);
|
|
if (useAppDataPath) path = Path.Combine(Application.persistentDataPath, "Library", fileName);
|
|
|
|
if (!File.Exists(path))
|
|
{
|
|
Debug.LogWarning($"[Library] File not found: {path}");
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
string json = File.ReadAllText(path);
|
|
T data = JsonHelper.FromJson<T>(json);
|
|
Debug.Log($"[Library] Loaded successfully: {fileName}");
|
|
return data;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"[Library] Failed to load {fileName}: {e.Message}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
#region Root Data Classes
|
|
|
|
/// <summary>
|
|
/// LibraryStakerCrane.json 루트 데이터 클래스
|
|
/// </summary>
|
|
[Serializable]
|
|
public class LibraryStakerCraneData
|
|
{
|
|
/// <summary>스태커 크레인 장비 목록</summary>
|
|
public List<EquipmentItem> list = new();
|
|
}
|
|
|
|
/// <summary>
|
|
/// LibraryRackSingle.json 루트 데이터 클래스
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// JSON 키가 "AGV"로 되어 있어 필드명이 AGV입니다.
|
|
/// </remarks>
|
|
[Serializable]
|
|
public class LibraryRackSingleData
|
|
{
|
|
/// <summary>랙 장비 목록 (JSON 키: "list")</summary>
|
|
public List<EquipmentItem> list = new();
|
|
}
|
|
|
|
/// <summary>
|
|
/// LibraryAGV.json 루트 데이터 클래스
|
|
/// </summary>
|
|
[Serializable]
|
|
public class LibraryAGVData
|
|
{
|
|
/// <summary>AGV 장비 목록</summary>
|
|
public List<EquipmentItem> list = new();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Equipment Item
|
|
|
|
/// <summary>
|
|
/// 개별 장비 항목 데이터
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 장비의 기본 정보, 3D 모델 경로, 속성 정의, 상태 정의를 포함합니다.
|
|
/// </remarks>
|
|
[Serializable]
|
|
public class EquipmentItem
|
|
{
|
|
/// <summary>
|
|
/// 장비 식별자
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 고유한 장비 ID (예: "SingleFork", "Rack_Single")
|
|
/// </remarks>
|
|
public string id;
|
|
|
|
/// <summary>
|
|
/// 표시 이름
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// UI에 표시되는 사람이 읽을 수 있는 이름 (예: "Single Fork")
|
|
/// </remarks>
|
|
public string label;
|
|
|
|
/// <summary>
|
|
/// glTF/GLB 파일 경로
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Resources/Studio 폴더 기준 상대 경로 (예: "staker_crane/SingleFork.glb")
|
|
/// </remarks>
|
|
public string gltf;
|
|
|
|
/// <summary>
|
|
/// 썸네일 이미지 경로 (선택)
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 라이브러리 UI에서 표시할 미리보기 이미지
|
|
/// </remarks>
|
|
public string image;
|
|
|
|
/// <summary>
|
|
/// 속성 정보 목록
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 장비의 각 파트별 속성 정의 (크기, 속도 등)
|
|
/// </remarks>
|
|
public List<PropertiesInfo> propertiesInfo = new();
|
|
|
|
/// <summary>
|
|
/// 상태 정보
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 네트워크 연결 상태, 설비 운영 상태 등의 정의
|
|
/// </remarks>
|
|
public StatusInfo statusInfo = new();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties Info
|
|
|
|
/// <summary>
|
|
/// 속성 정보 섹션
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 장비의 특정 파트(Frame, Fork 등)에 대한 속성 그룹을 정의합니다.
|
|
/// </remarks>
|
|
[Serializable]
|
|
public class PropertiesInfo
|
|
{
|
|
/// <summary>
|
|
/// 섹션 이름
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// UI에서 그룹핑 표시용 (예: "Frame", "Fork", "Carriage")
|
|
/// </remarks>
|
|
public string section;
|
|
|
|
/// <summary>
|
|
/// 3D 모델 파트 이름
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// glTF 모델 내의 노드/메시 이름 (예: "Fork_01", "Carriage_Body")
|
|
/// null이면 모델 전체에 적용
|
|
/// </remarks>
|
|
public string modelPart;
|
|
|
|
/// <summary>
|
|
/// 모델 타입
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <list type="bullet">
|
|
/// <item><description><b>General:</b> 일반 타입 - 단순 속성 표시</description></item>
|
|
/// <item><description><b>Stretch:</b> 스트레치 타입 - 크기 조절 가능</description></item>
|
|
/// <item><description><b>Repeat:</b> 반복 타입 - 반복 배치 가능 (레일 등)</description></item>
|
|
/// </list>
|
|
/// </remarks>
|
|
public string modelType;
|
|
|
|
/// <summary>
|
|
/// 속성 항목 목록
|
|
/// </summary>
|
|
public List<PropertyItem> properties = new();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 개별 속성 항목
|
|
/// </summary>
|
|
[Serializable]
|
|
public class PropertyItem
|
|
{
|
|
/// <summary>
|
|
/// 속성 ID (선택)
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 코드에서 속성을 식별하기 위한 고유 키 (예: "length", "forwardSpeed")
|
|
/// </remarks>
|
|
public string id;
|
|
|
|
/// <summary>
|
|
/// 표시 레이블
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// UI에 표시되는 속성 이름 (예: "너비", "주행 속도")
|
|
/// </remarks>
|
|
public string label;
|
|
|
|
/// <summary>
|
|
/// 데이터 타입
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// "Float" 또는 "Int"
|
|
/// </remarks>
|
|
public string type;
|
|
|
|
/// <summary>
|
|
/// 속성 값
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 숫자 값(int, float) 또는 벡터 문자열 (예: 150, 50.5, "0,0,1")
|
|
/// JSON에서 숫자 또는 문자열로 저장될 수 있음
|
|
/// </remarks>
|
|
public object value;
|
|
|
|
/// <summary>
|
|
/// 단위
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 표시용 단위 문자열 (예: "cm", "cm/s", "")
|
|
/// </remarks>
|
|
public string unit;
|
|
|
|
/// <summary>
|
|
/// value를 float로 반환합니다.
|
|
/// </summary>
|
|
public float GetFloatValue()
|
|
{
|
|
if (value == null) return 0f;
|
|
if (value is float f) return f;
|
|
if (value is double d) return (float)d;
|
|
if (value is int i) return i;
|
|
if (value is long l) return l;
|
|
if (float.TryParse(value.ToString(), out float result)) return result;
|
|
return 0f;
|
|
}
|
|
|
|
/// <summary>
|
|
/// value를 int로 반환합니다.
|
|
/// </summary>
|
|
public int GetIntValue()
|
|
{
|
|
if (value == null) return 0;
|
|
if (value is int i) return i;
|
|
if (value is long l) return (int)l;
|
|
if (value is float f) return (int)f;
|
|
if (value is double d) return (int)d;
|
|
if (int.TryParse(value.ToString(), out int result)) return result;
|
|
return 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// value를 string으로 반환합니다.
|
|
/// </summary>
|
|
public string GetStringValue()
|
|
{
|
|
return value?.ToString() ?? string.Empty;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Status Info
|
|
|
|
/// <summary>
|
|
/// 상태 정보 컨테이너
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 장비의 네트워크 연결 상태와 운영 상태를 정의합니다.
|
|
/// </remarks>
|
|
[Serializable]
|
|
public class StatusInfo
|
|
{
|
|
/// <summary>
|
|
/// 네트워크 상태 정의
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 상위 시스템과의 통신 상태 (connect/disconnect)
|
|
/// null이면 네트워크 상태 미지원
|
|
/// </remarks>
|
|
public StatusSection network;
|
|
|
|
/// <summary>
|
|
/// 설비 상태 정의
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 장비의 운영 상태 (normal, running, error 등)
|
|
/// </remarks>
|
|
public StatusSection equipment;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 상태 섹션
|
|
/// </summary>
|
|
[Serializable]
|
|
public class StatusSection
|
|
{
|
|
/// <summary>
|
|
/// 섹션 이름
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// UI 표시용 (예: "상위 통신 상태", "설비 상태")
|
|
/// </remarks>
|
|
public string section;
|
|
|
|
/// <summary>
|
|
/// 상태 속성 목록
|
|
/// </summary>
|
|
public List<StatusProperty> properties = new();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 개별 상태 속성
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 특정 상태 값에 대한 정의 (이름, 색상 등)
|
|
/// </remarks>
|
|
[Serializable]
|
|
public class StatusProperty
|
|
{
|
|
/// <summary>
|
|
/// 상태 값 (매칭용)
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 실제 데이터와 매칭하기 위한 값 (예: "0", "1", "true", "false")
|
|
/// </remarks>
|
|
public string label;
|
|
|
|
/// <summary>
|
|
/// 상태 이름
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 상태의 의미를 나타내는 이름 (예: "normal", "running", "error", "connect")
|
|
/// </remarks>
|
|
public string stat;
|
|
|
|
/// <summary>
|
|
/// 상태 색상 (HEX 코드)
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// UI 표시용 색상 코드 (예: "#228B22" - 녹색, "#8B0000" - 빨간색)
|
|
/// 빈 문자열이면 기본 색상 사용
|
|
/// </remarks>
|
|
public string? value;
|
|
}
|
|
|
|
#endregion
|
|
}
|