mqtt,api 연결 설정 작업 #78

Merged
UVCXR merged 2 commits from njh/250516_00 into main 2025-05-19 13:53:33 +09:00
23 changed files with 705 additions and 0 deletions
Showing only changes of commit a74afe0319 - Show all commits

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 90095164b80e1ac428e5e93715a05704
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,36 @@
using Newtonsoft.Json;
using NUnit.Framework;
using System;
using UnityEngine;
namespace Studio.Auth
{
public enum AuthEntitiState
{
Login, Logout, Loading, Error
}
#nullable enable
public class AuthEntity
{
public AuthEntitiState State { get; set; }
public string? accessToken { get; set; }
public string? message { get; set; }
public AuthEntity Copy()
{
return new AuthEntity()
{
State = this.State,
accessToken = this.accessToken,
message = this.message
};
}
public override string ToString()
{
return $"AuthEntity State:{State}, accessToken:{accessToken} message:{message}";
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 26179ff2e39783a4a80005112ece2abf

View File

@@ -0,0 +1,60 @@
using Best.HTTP;
using Studio.Conifg;
using Studio.Setting.Connect;
using System.Collections.Generic;
using System.Security.Policy;
using System;
using System.Threading.Tasks;
using UnityEngine;
namespace Studio.Auth
{
#nullable enable
public class AuthRepository
{
internal async Task<AuthEntity> Login(string email, string password)
{
return await LoginRemote(email, password);
}
private async Task<AuthEntity> LoginRemote(string email, string password)
{
try
{
var response = await RestAPI.RequestPost<ResponseModel<AuthEntity>>("/api/auth/token",
new Dictionary<string, object>
{
["email"] = email,
["password"] = password,
}, false);
if (response.code == "SUCCESS")
return new AuthEntity() { State = AuthEntitiState.Login, accessToken = response.data.accessToken };
else
return new AuthEntity() { State = AuthEntitiState.Error, message = response.message };
}
catch (AsyncHTTPException e)
{
Debug.LogError($"LoginRemote {e}");
string message = e.ToString();
try
{
ResponseModel<object> response = JsonUtility.FromJson<ResponseModel<object>>(e.Content);
message = response.message;
}
catch (Exception ex)
{
Debug.LogWarning($"LoginRemote {ex}");
}
;
return new AuthEntity() { State = AuthEntitiState.Error, message = message };
}
catch (Exception e)
{
Debug.LogError($"LoginRemote {e}");
return new AuthEntity() { State = AuthEntitiState.Error, message = e.Message };
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3c37a4285d6a9374c9a22f9d132460e7

View File

@@ -0,0 +1,79 @@
using Newtonsoft.Json;
using NUnit.Framework;
using System;
using System.Threading.Tasks;
using UnityEngine;
namespace Studio.Auth
{
public class AuthService
{
#region Singleton
private static readonly AuthService instance = new AuthService(new AuthRepository());
public static AuthService Instance => instance;
static AuthService() { }
#endregion
public EventHandler<AuthEntity> OnChanged { get; set; }
public AuthEntity Entiti { get => entiti; }
private AuthEntity entiti;
private readonly AuthRepository _repository;
private AuthService(AuthRepository repository)
{
entiti = new AuthEntity()
{
State = AuthEntitiState.Logout,
};
_repository = repository;
}
public async Task Login(string email, string password)
{
// Simulate login
if (email.Length > 0 && password.Length > 0)
{
entiti = new AuthEntity()
{
State = AuthEntitiState.Loading,
};
//OnChanged?.Invoke(this, entiti.Copy());
entiti = await _repository.Login(email, password);
//OnChanged?.Invoke(this, entiti.Copy());
}
}
public void Logout()
{
entiti = new AuthEntity()
{
State = AuthEntitiState.Logout,
};
OnChanged?.Invoke(this, entiti.Copy());
}
public void LoginTest()
{
entiti = new AuthEntity()
{
State = AuthEntitiState.Login,
accessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiZW1haWwiOiJzeXN0ZW0iLCJncm91cElkIjoxLCJncm91cENvZGUiOm51bGwsIm5hbWUiOiLsi5zsiqTthZwg6rSA66as7J6QIiwibGljZW5zZSI6InByb3ZpZGVyIiwicGhvbmUiOiIwMTAtMDAwMC0wMDAwIiwibWVudUF1dGgiOiJTeXN0ZW1BZG1pbiIsImlhdCI6MTc0NTIwMDg5MH0.voyvIbZH7OXBdfIy1gug53Z0LLK3AwJR5OBE_X1w4Bg",
};
}
/// <summary>
/// 바뀐 로그인 프로세스. Unity 실행 시 token을 전달 받아 호출
/// </summary>
/// <param name="token"></param>
public void LoginDirect(string token)
{
entiti = new AuthEntity()
{
State = AuthEntitiState.Login,
accessToken = token
};
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0feb2cf99fab3204c9ba243a03a3afe3

View File

@@ -0,0 +1,34 @@
using Newtonsoft.Json;
using NUnit.Framework;
using System.Collections.Generic;
using UnityEngine;
namespace Studio
{
public class ConnectedEntity
{
[JsonProperty("APIs")]
public List<APIEntity> apiS { get; set; }
[JsonProperty("MQTTs")]
public List<MQTTEntity> mqtts { get; set; }
}
public class APIEntity
{
[JsonProperty("APIDomain")]
public string APIDomain { get; set; }
[JsonProperty("Port")]
public int APIPort { get; set; }
[JsonProperty("URL")]
public List<string> url { get; set; }
}
public class MQTTEntity
{
[JsonProperty("MQTTDomain")]
public string MQTTDomain { get; set; }
[JsonProperty("Port")]
public int MQTTPort { get; set; }
[JsonProperty("Topics")]
public List<string> topics { get; set; }
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fbcc575483b274d4382f6a2b8d380d11

View File

@@ -0,0 +1,15 @@
using UnityEngine;
namespace Studio.Conifg
{
public class Constants
{
public static string APIDomain;
public static int APIPort;
public static string MQTTDomain;
public static int MQTTPort;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: dcc5387e9d1cf9943b3e1d36a9b2bf17

View File

@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Studio
{
public interface IStudioEntity
{
public string Id { get; }
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4661549154da4794f8e145234f7f4566

View File

@@ -0,0 +1,57 @@
using UnityEngine;
namespace Studio.Setting.Connect
{
/// <summary>
/// API Domain,URL, Port Setting
/// </summary>
///
public enum APIState
{
Loaded, Loading, Error
}
[System.Serializable]
public class ResponseModel<T>
{
public int status;
public string code;
public string message;
public T data;
public override string ToString()
{
return $"ResponseModel status:{status}, code:{code} message:{message} data:{data}";
}
}
/// <summary>
/// Data로 한번 감싸진 response때문에
/// </summary>
/// <typeparam name="T"></typeparam>
[System.Serializable]
public class ResponseDataModel<T>
{
public T data;
}
public class EntityWithState<T>
{
private APIState state;
public APIState State { get => state; }
private T entity;
public T Entity { get => entity; }
private string? message;
public string? Message { get => message; }
public EntityWithState(APIState state, T entity, string? message = null)
{
this.state = state;
this.entity = entity;
this.message = message;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ac67491ed4be8f44c820f2d1ac95b730

View File

@@ -0,0 +1,81 @@
using Best.HTTP;
using Studio.Auth;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Studio.Conifg
{
public class RestAPI
{
public static async Task<T> RequestPost<T>(string url, Dictionary<string, object> body = null, bool useAuth = true)
{
return await Request<T>(url, body, HTTPMethods.Post, useAuth);
}
public static async Task<T> RequestGet<T>(string url, Dictionary<string, object> body = null, bool useAuth = true)
{
return await Request<T>(url, body, HTTPMethods.Get, useAuth);
}
private static async Task<T> Request<T>(string url, object body = null, HTTPMethods method = HTTPMethods.Post, bool useAuth = true)
{
string bodyString = "";
if (body != null)
{
if (body is string)
{
bodyString = body.ToString();
}
else if (body is Dictionary<string, object>)
{
bodyString = JsonUtility.ToJson(body);
}
}
url = $"http://{Constants.APIDomain}:{Constants.APIPort}{url}";
var request = SelectHTTPRequest(method, url);
request.DownloadSettings = new Best.HTTP.Request.Settings.DownloadSettings() { ContentStreamMaxBuffered = 1024 * 1024 * 200 };
request.MethodType = method;
request.SetHeader("Content-Type", "application/json; charset=utf-8");
if (useAuth) request.SetHeader("access-token", AuthService.Instance.Entiti.accessToken);
if (body != null)
{
if (body is string)
{
request.UploadSettings.UploadStream =
new MemoryStream(Encoding.UTF8.GetBytes(body as string));
}
else if (body is Dictionary<string, object>)
{
request.UploadSettings.UploadStream =
new MemoryStream(Encoding.UTF8.GetBytes(JsonUtility.ToJson(body)));
}
}
var response = await request.GetAsStringAsync();
return JsonUtility.FromJson<T>(response);
}
private static HTTPRequest SelectHTTPRequest(HTTPMethods methods, string url, OnRequestFinishedDelegate onRequest = null)
{
switch (methods)
{
case HTTPMethods.Get:
if (onRequest != null)
return HTTPRequest.CreateGet(url, onRequest);
else
return HTTPRequest.CreateGet(url);
case HTTPMethods.Post:
return HTTPRequest.CreatePost(url);
}
return null;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: be7e1446b881dce40aa1890c7219dc01

View File

@@ -0,0 +1,27 @@
using System;
using UnityEngine;
#nullable enable
namespace Studio.Setting.Connect
{
/// <summary>
/// MQTT Connect Setting
/// </summary>
public class StudioEntityWithState<T>
{
private APIState state;
public APIState State { get => state; }
private string? entity;
public string Entity { get => entity; }
private string? message;
public string? Message { get => message; }
public StudioEntityWithState(APIState state, string entity , string? message = null)
{
this.state = state;
this.entity = entity;
this.message = message;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ceeafcd53e4a2504f98183ba2fc8ba40

View File

@@ -0,0 +1,183 @@
using Best.HTTP.JSON.LitJson;
using Best.MQTT;
using Best.MQTT.Packets.Builders;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RTG;
using Studio.Conifg;
using Studio.Setting.Connect;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
namespace Studio
{
public class StudioRepoistory
{
private MQTTClient client;
public Action<string, Dictionary<string, Dictionary<string, string>>> OnTopicList;
public StudioRepoistory()
{
MQTTCreateConnect();
Application.quitting += OnDestroy;
}
public void MQTTCreateConnect()
{
Debug.Log($"MQTT Domain{Constants.MQTTDomain} , MQTTPORT{Constants.MQTTPort}");
var options = new ConnectionOptionsBuilder()
.WithTCP(Constants.MQTTDomain, Constants.MQTTPort)
.Build();
if (client != null)
{
client.OnConnected -= OnConnectedMQTT;
client.OnStateChanged -= OnStateChangedMQTT;
client.OnDisconnect -= OnDisconnectedMQTT;
client.OnError -= OnErrorMQTT;
}
client = new MQTTClient(options);
client.OnConnected += OnConnectedMQTT;
client.OnStateChanged += OnStateChangedMQTT;
client.OnDisconnect += OnDisconnectedMQTT;
client.OnError += OnErrorMQTT;
}
private ConnectPacketBuilder ConnectPacketBuilderCallback(MQTTClient client, ConnectPacketBuilder builder)
{
return builder.WithKeepAlive(60 * 60);//keep alive 1시간으로
}
public async Task<StudioEntityWithState<string>> BaseInfo(string url)
{
try
{
return await Task.Run<StudioEntityWithState<string>>(async () =>
{
ResponseModel<string> response = await RestAPI.RequestPost<ResponseModel<string>>(url);
if (response.code == "SUCCESS")
return new StudioEntityWithState<string>(APIState.Loaded, response.data);
return new StudioEntityWithState<string>(APIState.Error, null, response.message);
});
}
catch (Exception e)
{
return new StudioEntityWithState<string>(APIState.Error, null, e.Message);
}
}
public void MQTTConnect()
{
client.BeginConnect(ConnectPacketBuilderCallback);
}
private void OnErrorMQTT(MQTTClient client, string error)
{
Debug.Log($"OnError reason: '{error}'");
}
private void OnDisconnectedMQTT(MQTTClient client, DisconnectReasonCodes reasonCode, string reasonMessage)
{
if (Application.isPlaying)
{
}
}
private void OnStateChangedMQTT(MQTTClient client, ClientStates oldState, ClientStates newState)
{
Debug.Log($"{oldState} => {newState}");
}
private void OnConnectedMQTT(MQTTClient client)
{
Debug.Log($"MQTT OnConnected");
}
/// <summary>
/// 구독
/// </summary>
/// <param name="topic"></param>
public void Subscribe(string topic)
{
client.CreateBulkSubscriptionBuilder()
.WithTopic(new SubscribeTopicBuilder(topic).WithMessageCallback(OnTopic))
.BeginSubscribe();
}
/// <summary>
/// 구독 취소
/// </summary>
/// <param name="topic"></param>
public void UnSubscibe(string topic)
{
client.CreateUnsubscribePacketBuilder(topic)
.WithAcknowledgementCallback((client, topicFilter, reasonCode) => Debug.Log($"Unsubscribe request to topic filter '{topicFilter}' returned with code: {reasonCode}"))
.BeginUnsubscribe();
}
private void OnTopic(MQTTClient client, SubscriptionTopic topic, string topicName, ApplicationMessage message)
{
string payload = Encoding.UTF8.GetString(message.Payload.Data, message.Payload.Offset, message.Payload.Count);
var type = topic.Filter.ToString();
JObject json = JObject.Parse(payload);
foreach(JProperty prop in json.Children())
{
string key = prop.Name.ToString();
string value = prop.Value.ToString();
Debug.Log($"kEY : {key}, Value:{value}");
}
var T = json["data"];
var split = T.ToString().Split('[');
var t = $"[{split[1]}";
Debug.Log(T.ToString());
JArray jarray = JArray.Parse(t);
var list = new Dictionary<string, Dictionary<string, string>>();
foreach (JObject obj in jarray.Children())
{
Dictionary<string, string> keyvalue = new();
int index = 0;
string id = string.Empty;
foreach (JProperty prop in obj.Children())
{
string key = prop.Name.ToString();
string value = prop.Value.ToString();
if (index == 0)
{
list.Add(value, new());
id = value;
}
keyvalue.Add(key, value);
// Debug.Log($"kEY : {key}, Value:{value}");
}
list[id] = keyvalue;
}
//JObject json2 = JObject.Parse(jarray.ToString());
CheckUpdate(type, list);
//var json = JsonConvert.SerializeObject(t.ToString());
}
private void CheckUpdate(string type, Dictionary<string, Dictionary<string, string>> data)
{
if (data == null)
return;
if(data.Count > 0)
OnTopicList?.Invoke(type, data);
}
private void OnDestroy()
{
client?.CreateDisconnectPacketBuilder()
.WithReasonCode(DisconnectReasonCodes.NormalDisconnection)
.WithReasonString($"{Constants.MQTTDomain} Disconnecting")
.BeginDisconnect();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 08b6f925bff664748b4509de6c5f2e66

View File

@@ -0,0 +1,94 @@
using System.Collections.Generic;
using System;
using UnityEngine;
using Studio;
using System.Threading.Tasks;
using Studio.Setting.Connect;
namespace Studio
{
public class StudioServiceTypeEventArgs : EventArgs
{
public readonly string Type;
List<IStudioEntity> Entitis;
public StudioServiceTypeEventArgs(string type, List<IStudioEntity> entity)
{
Type = type;
Entitis = entity;
}
}
public class StudioService
{
#region Singleton
private static readonly StudioService instance = new StudioService(new StudioRepoistory());
public static StudioService Instance => instance;
static StudioService() { }
#endregion
private Dictionary<string, EventHandler<StudioServiceTypeEventArgs>> listenerTypeMap;
private StudioRepoistory repository;
private StudioService(StudioRepoistory repository)
{
this.repository = repository;
listenerTypeMap = new Dictionary<string, EventHandler<StudioServiceTypeEventArgs>>();
repository.OnTopicList += OnTopicList;
// repository.OnDetectedError += OnDetectedError;
}
private void OnTopicList(string type, Dictionary<string, Dictionary<string, string>> entities)
{
foreach(var idKey in entities)
{
UpdateTopicData(type, idKey.Value);
}
}
private void UpdateTopicData(string type, Dictionary<string, string> entity)
{
}
public void AddTypeListener(string type, EventHandler<StudioServiceTypeEventArgs> listener)
{
if (!listenerTypeMap.ContainsKey(type))
{
listenerTypeMap[type] = listener;
}
else
{
listenerTypeMap[type] += listener;
}
}
public async Task LoadBaeData(string url)
{
StudioEntityWithState<string> data = await repository.BaseInfo(url);
if(data.State.Equals(APIState.Loaded))
{
//Debug.Log("Test");
//UpdateEntity<IStudioEntity>(data.Entity);
}
else if(data.State == APIState.Error)
{
//TODO 에러메세지
Debug.Log($"APIState : Error , Message :{data.Message}");
}
}
public void UpdateEntity(IStudioEntity data)
{
//DispatchMachineEvent()
}
private void DispatchMachineEvent(string type, List<IStudioEntity> entity)
{
if (listenerTypeMap.ContainsKey(type))
{
listenerTypeMap[type].Invoke(this, new StudioServiceTypeEventArgs(type, entity));
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 12899bf44d0ad334fac9db78807dca00