using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using UdpClientLib; using UnityEngine; /// /// ¾îÇø®ÄÉÀ̼ÇÀÇ ÇÙ½É ºñÁî´Ï½º ·ÎÁ÷°ú µ¥ÀÌÅ͸¦ °ü¸®ÇÏ´Â ¸ðµ¨ Ŭ·¡½º /// ·Îº¿ Á¦¾î±â¿ÍÀÇ TCP/UDP Åë½ÅÀ» ´ã´çÇϸç, ·Îº¿ÀÇ »óÅ Á¶È¸, À̵¿ ¸í·É Àü¼Û, /// ÇÁ·Î±×·¥(Job) ÆÄÀÏÀÇ »ý¼º/¼öÁ¤/»èÁ¦/½ÇÇà ±â´ÉÀ» ¼öÇà /// /// public class ProgramModel { private string tcpBaseUrl; private string udpBaseUrl; HttpClient httpClient = new HttpClient(); private SingleTcpClient tcpClient; private SingleUdpClient udpClient; public UdpClientManager manager = new UdpClientManager(); // --- µ¥ÀÌÅÍ Ä³½Ã --- private List allProgramsCache = new List(); // ·Îº¿¿¡ ÀúÀåµÈ ÇÁ·Î±×·¥ ¸ñ·Ï ij½Ã public RobotProgram CurrentProgram { get; private set; } // ÇöÀç ·ÎµåµÈ ÇÁ·Î±×·¥ Á¤º¸ private RobotData robotData; // --- µ¿±âÈ­ ¹× »óÅ º¯¼ö --- private readonly object lockObject = new object(); // ½º·¹µå °£ µ¥ÀÌÅÍ Á¢±Ù µ¿±âÈ­¸¦ À§ÇÑ ¶ô °´Ã¼ private bool hasNewData; // »õ µ¥ÀÌÅÍ ¼ö½Å ¿©ºÎ Ç÷¡±× public bool IsMoving; // ÇöÀç ·Îº¿ÀÌ À̵¿ ÁßÀÎÁö ¿©ºÎ public bool isError; // ÇöÀç ·Îº¿ÀÌ ¿¡·¯ »óÅÂÀÎÁö ¿©ºÎ private Vector3 startMovementPosition; // À̵¿ ½ÃÀÛ Àü ¸ñÇ¥ À§Ä¡ (UDP ÆÐŶ °ËÁõ¿ë) private int currentToolNum = 0; // ÇöÀç ·Îº¿¿¡ ¼³Á¤µÈ Åø ¹øÈ£ public bool isMoveAllowed = true; // Åë½Å Çã¿ë ¿©ºÎ Ç÷¡±× public event Action OnRobotErrorStateChanged; // ¿¡·¯ »óÅ º¯°æ ½Ã ¾Ë¸²À» ÁÖ±â À§ÇÑ À̺¥Æ® /// /// »ý¼ºÀÚ: Åë½Å URLÀ» ¼³Á¤ÇÏ°í ºñµ¿±â ¿¬°á ÀÛ¾÷À» ½ÃÀÛ /// public ProgramModel(string hostip, int tcpPort, int udpPort, RobotController robotController) { tcpBaseUrl = $"http://{hostip}:{tcpPort}"; udpBaseUrl = $"http://{hostip}:{udpPort}"; _ = HandleAsyncWork(hostip, tcpPort, udpPort); } /// /// TCP ¹× UDP Ŭ¶óÀÌ¾ðÆ®¸¦ ÃʱâÈ­ÇÏ°í ¿¬°á /// public async Task HandleAsyncWork(string hostip, int tcpPort, int udpPort) { tcpClient = new SingleTcpClient(); await tcpClient.ConnectAsync("Tcp-Client", hostip, tcpPort); udpClient = manager.AddClient("Udp-client", hostip, udpPort); manager.PrintStatus(); } /// /// ¾îÇø®ÄÉÀÌ¼Ç ½ÃÀÛ ½Ã ÇÊ¿äÇÑ Ãʱ⠵¥ÀÌÅ͸¦ ·Îµå /// (ÇÁ·Î±×·¥ ¸ñ·Ï, Åø ¹øÈ£ µî) /// public async Task InitializeAsync() { await LoadAllPrograms(); hasNewData = true; IsMoving = false; isError = false; try { currentToolNum = await GetRobotToolNum(); } catch (Exception e) { Debug.LogWarning($"ÇöÀç Åø ¹øÈ£¸¦ °¡Á®¿À´Â µ¥ ½ÇÆÐÇß½À´Ï´Ù: {e.Message}. ±âº»°ª(0)À» »ç¿ëÇÕ´Ï´Ù."); currentToolNum = 1; } return; } /// /// UI(ProgramPresenter)¿¡¼­ ÃֽŠ·Îº¿ µ¥ÀÌÅͰ¡ ÀÖ´ÂÁö È®ÀÎÇÒ ¶§ »ç¿ë /// public bool IsNewDataAvailable() { return robotData != null; } /// /// ¹é±×¶ó¿îµå ½º·¹µå¿¡¼­ °»½ÅµÈ ÃֽŠ·Îº¿ µ¥ÀÌÅ͸¦ ¾ÈÀüÇÏ°Ô °¡Á®¿È /// public RobotData GetLatestRobotData() { lock (lockObject) { //hasNewData = false; // µ¥ÀÌÅ͸¦ ÀоúÀ¸¹Ç·Î Ç÷¡±×¸¦ ³»¸² (ÃÖÀûÈ­¸¦ À§ÇØ ÁÖ¼® 󸮵Ê: ¾ðÁ¦µç ÀÐÀ» ¼ö ÀÖ°Ô ÇÔ) return robotData; } } /// /// ÇÁ·Î±×·¥ »ý¼º/ºÒ·¯¿À±â ½Ã½ºÅÛ /// public async Task CheckProgramExists(string jobProgramName) { string requestUri = $"{tcpBaseUrl}/file_manager/file_exist?pathname=project/jobs/{jobProgramName}"; HttpResponseMessage result = await httpClient.GetAsync(requestUri); string jsonResponse = await result.Content.ReadAsStringAsync(); return jsonResponse.Equals("true"); } public async Task CreateNewProgram(string userInputId) { string robotModelName; if (string.IsNullOrEmpty(userInputId)) return false; string newProgramId = $"{userInputId}.job"; if (await CheckProgramExists(newProgramId)) { Debug.LogError("ÆÄÀÏÀÌ ÀÌ¹Ì Á¸ÀçÇÕ´Ï´Ù."); return false; } else Debug.Log($"{newProgramId} »ý¼º"); try { robotModelName = await GetRobotModelNameAsync(); } catch (Exception e) { Debug.LogError($"·Îº¿ ¸ðµ¨¸íÀ» °¡Á®¿À´Â µ¥ ½ÇÆÐÇß½À´Ï´Ù: {e.Message}"); return false; } NewJobRequestDTO newJob = new NewJobRequestDTO { fname = newProgramId, model_name = robotModelName, n_add_ax = 0 }; string jsonString = JsonConvert.SerializeObject(newJob); HttpContent jsonPayload = new StringContent(jsonString, Encoding.UTF8, "application/json"); string requestUri = $"{tcpBaseUrl}/project/jobs/create_job"; try { HttpResponseMessage result = await httpClient.PostAsync(requestUri, jsonPayload); if (result.IsSuccessStatusCode) { await LoadProgram(newProgramId); object payload = CmdLinePayload("end", 0); jsonString = JsonConvert.SerializeObject(payload); jsonPayload = new StringContent(jsonString, Encoding.UTF8, "application/json"); HttpResponseMessage jsonResponse = await httpClient.PostAsync( $"{tcpBaseUrl}/project/jobs/{CurrentProgram.ProgramId}/ins_cmd_line", jsonPayload); if (jsonResponse.IsSuccessStatusCode) return true; else return false; } else return false; } catch (Exception e) { Debug.LogError($"ÇÁ·Î±×·¥ »ý¼º ½ÇÆÐ: {userInputId}, {e.Message}"); return false; } } private async Task GetRobotModelNameAsync() { string requestUri = $"{tcpBaseUrl}/project/rgen"; HttpResponseMessage result = await httpClient.GetAsync(requestUri); string jsonResponse = await result.Content.ReadAsStringAsync(); JObject data = JObject.Parse(jsonResponse); string modelName = (string)data.SelectToken("robot_model"); if (string.IsNullOrEmpty(modelName)) { throw new Exception("·Îº¿ »óÅ API ÀÀ´ä¿¡¼­ ¸ðµ¨¸íÀ» ãÀ» ¼ö ¾ø½À´Ï´Ù."); } return modelName; } public async Task GetRobotMotorStateAsync() { string requestUri = $"{tcpBaseUrl}/project/rgen"; HttpResponseMessage result = await httpClient.GetAsync(requestUri); string jsonResponse = await result.Content.ReadAsStringAsync(); JObject data = JObject.Parse(jsonResponse); int motorState = (int)data.SelectToken("enable_state"); if (motorState == 0 || motorState == 256) return true; else if (motorState == 1) return false; else { throw new Exception("·Îº¿ »óÅ API ÀÀ´ä¿¡¼­ ¸ðÅÍ »óŸ¦ ãÀ» ¼ö ¾ø½À´Ï´Ù."); } } public async Task GetRobotToolNum() { string requestUri = $"{tcpBaseUrl}/project/rgen"; HttpResponseMessage result = await httpClient.GetAsync(requestUri); string jsonResponse = await result.Content.ReadAsStringAsync(); JObject data = JObject.Parse(jsonResponse); int toolNum = (int)data.SelectToken("tool_no"); return toolNum; } /// /// [¹é±×¶ó¿îµå ·çÇÁ 1] TCP¸¦ ÅëÇØ ·Îº¿ÀÇ ÇöÀç ÁÂÇ¥¸¦ ÁÖ±âÀûÀ¸·Î Á¶È¸ /// public async Task GetTCPAsync(CancellationToken token) { while (!token.IsCancellationRequested) { try { string requestUri = $"{tcpBaseUrl}/project/robot/po_cur"; HttpResponseMessage result = await httpClient.GetAsync(requestUri); string jsonResponse = await result.Content.ReadAsStringAsync(); var tempRobotData = JsonConvert.DeserializeObject(jsonResponse, new JsonSerializerSettings { CheckAdditionalContent = false }); lock (lockObject) { robotData = tempRobotData; hasNewData = true; } await Task.Delay(10, token); } catch (Exception e) { Debug.Log(e); await Task.Delay(1000); // ¿¡·¯ ½Ã ´õ ±ä ´ë±â } } } /// /// ¼­¹ö¿¡¼­ ƯÁ¤ ÇÁ·Î±×·¥ÀÇ ³»¿ëÀ» ·ÎµåÇÏ¿© CurrentProgram¿¡ ÀúÀå /// public async Task LoadProgram(string programId) { string requestUri = $"{tcpBaseUrl}/file_manager/files?pathname=project/jobs/{programId}&common"; HttpResponseMessage result = await httpClient.GetAsync(requestUri); string rawTextContent = await result.Content.ReadAsStringAsync(); if (string.IsNullOrEmpty(rawTextContent)) { return false; } CurrentProgram = new RobotProgram(programId, rawTextContent); return true; } /// /// ¼­¹ö¿¡ ÀúÀåµÈ ¸ðµç Job ÇÁ·Î±×·¥ ¸ñ·ÏÀ» °¡Á®¿Í ij½Ã /// private async Task LoadAllPrograms() { allProgramsCache.Clear(); string jsonResponse = null; string wrappedJson = null; try { HttpResponseMessage result = await httpClient.GetAsync($"{tcpBaseUrl}/project/jobs_info"); jsonResponse = await result.Content.ReadAsStringAsync(); wrappedJson = $"{{\"jobs\":{jsonResponse}}}"; JobListWrapper wrapper = JsonUtility.FromJson(wrappedJson); if (wrapper != null && wrapper.jobs != null) { allProgramsCache = wrapper.jobs; } else { Debug.LogError("¼­¹ö¿¡¼­ job ¸ñ·ÏÀ» ÆÄ½ÌÇÒ ¼ö ¾ø½À´Ï´Ù: " + jsonResponse); } } catch (ArgumentException e) { Debug.LogError($"JSON ÆÄ½Ì ¿À·ù: {e.Message}"); } catch (Exception e) { Debug.LogError($"LoadAllPrograms ¾Ë ¼ö ¾ø´Â ¿À·ù: {e.Message}"); } } public async Task> GetAllProgramIds() { await LoadAllPrograms(); List ids = new List(); foreach (var jobInfo in allProgramsCache) { ids.Add(jobInfo.fname); } return ids; } /// /// ·Îº¿ À§Ä¡ ±â·Ï ½Ã½ºÅÛ /// // ÇÁ·Î±×·¥¿¡ ¸í·É¹® Ãß°¡ public object CmdLinePayload(string cmd_line, int sno_ref) { object payload; if (cmd_line == "end") { payload = new { cmd_line = cmd_line, sno_ref = sno_ref }; } else { payload = new { adjust_branch = true, cmd_line = cmd_line, fno_ref = -1, mechinfo = -1, move_cursor = 1, ofs = 0, save_file = true, set_cur_pose = 1, sno_ref = sno_ref }; } return payload; } /// /// ÇöÀç ·Îº¿ÀÇ À§Ä¡¸¦ ÇÁ·Î±×·¥ÀÇ ½ºÅÜÀ¸·Î ÀúÀåÇϰųª, ±âÁ¸ ½ºÅÜÀ» ¼öÁ¤ /// index°¡ -1À̸é Ãß°¡, 0 ÀÌ»óÀ̸é ÇØ´ç À妽ºÀÇ ½ºÅÜ ¼öÁ¤ /// public async Task SavePointToProgramAsync(RobotData pointData, int index = -1) { if (CurrentProgram == null) { Debug.LogError("ÀúÀåÇÒ ÇÁ·Î±×·¥ÀÌ ·ÎµåµÇÁö ¾Ê¾Ò½À´Ï´Ù."); return false; } string jsonPayload; string requestUri; HttpContent content; object payload; try { if (index == -1) { // sno_ref = ¸¶Áö¸· ½ºÅÜÀÇ ¹øÈ£, ¾øÀ¸¸é 0 int sno_ref = (CurrentProgram.Steps.Count > 0) ? CurrentProgram.Steps.Last().StepNumber : 0; payload = CmdLinePayload($"move P,spd=50%,accu=3,tool={currentToolNum}", sno_ref); requestUri = $"{tcpBaseUrl}/project/jobs/{CurrentProgram.ProgramId}/ins_cmd_line"; } else { // sno = ¼öÁ¤ÇÒ ½ºÅÜÀÇ ¹øÈ£ int sno = CurrentProgram.Steps[index].StepNumber; payload = new { mechinfo = -1, save_file = true, sno = sno }; requestUri = $"{tcpBaseUrl}/project/jobs/{CurrentProgram.ProgramId}/pose_modify"; } jsonPayload = JsonConvert.SerializeObject(payload); content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); HttpResponseMessage result = await httpClient.PostAsync(requestUri, content); if (result.IsSuccessStatusCode) { // ¼­¹ö ÀúÀåÀÌ ¼º°øÇϸé, ·ÎÄà ij½Ã¿¡µµ ¹Ý¿µ if (index == -1) CurrentProgram.AddStep(pointData, currentToolNum); // ·ÎÄà ij½Ã¿¡ Ãß°¡ else CurrentProgram.UpdateStep(index, pointData); // ·ÎÄà ij½Ã ¼öÁ¤ return true; } Debug.LogError($"SavePointToProgramAsync ½ÇÆÐ: {await result.Content.ReadAsStringAsync()}"); return false; } catch (Exception e) { Debug.LogError($"Æ÷ÀÎÆ® ÀúÀå ½ÇÆÐ: {e.Message}"); return false; } } /// /// ÇÁ·Î±×·¥¿¡¼­ ƯÁ¤ ½ºÅÜÀ» »èÁ¦ /// public async Task DeletePointFromProgramAsync(int index) { if (CurrentProgram == null || index < 0 || index >= CurrentProgram.Steps.Count) { Debug.LogError($"DeletePointFromProgramAsync: À߸øµÈ À妽º {index}"); return false; } // sno_ref = »èÁ¦ÇÒ ½ºÅÜÀÇ ¹øÈ£ (0-based index°¡ ¾Æ´Ô) int sno_ref = CurrentProgram.Steps[index].StepNumber; var payload = new { adjust_branch = true, fno_ref = 0, ofs = 0, save_file = true, sno_ref = sno_ref }; string jsonPayload = JsonConvert.SerializeObject(payload); HttpContent content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); string requestUri = $"{tcpBaseUrl}/project/jobs/{CurrentProgram.ProgramId}/del_cmd_line"; try { HttpResponseMessage result = await httpClient.PostAsync(requestUri, content); if (result.IsSuccessStatusCode) { // ¼­¹ö »èÁ¦ ¼º°ø ½Ã, ·ÎÄà ij½Ã¿¡¼­µµ »èÁ¦ ¹× ÀçÁ¤·Ä CurrentProgram.DeleteStep(index); return true; } Debug.LogError($"DeletePointFromProgramAsync ½ÇÆÐ: {await result.Content.ReadAsStringAsync()}"); return false; } catch (Exception e) { Debug.LogError($"Æ÷ÀÎÆ® »èÁ¦ ½ÇÆÐ: {e.Message}"); return false; } } // Ÿ°Ù Æ÷Áö¼ÇÀ¸·Î À̵¿ public async Task MoveToPoseTcpAsync(Vector3 position) { //await clientLock.WaitAsync(); if (!isMoveAllowed) { Debug.Log("TCP Àü¼Û Áß´Ü"); return false; } try { startMovementPosition.x = Convert.ToSingle(Math.Round(-1 * position.x * 1000, 2)); startMovementPosition.y = Convert.ToSingle(Math.Round(-1 * position.z * 1000, 2)); startMovementPosition.z = Convert.ToSingle(Math.Round(position.y * 1000, 2)); float currentRx = -180f; float currentRy = 0f; float currentRz = 180f; string jsonResponse = await tcpClient.SendPostRequestAsync("/project/robot/move_to_pose_manual", $"{{\"pose_tg\":{{\"crd\":\"robot\",\"_type\":\"Pose\",\"mechinfo\":1,\"x\":{startMovementPosition.x},\"y\":{startMovementPosition.y},\"z\":{startMovementPosition.z}, \"rx\":{currentRx}, \"ry\":{currentRy}, \"rz\":{currentRz}}}}}"); if (jsonResponse.Contains("200")) { Debug.Log("TCP POST (Move) ¸í·É Àü¼Û ¼º°ø"); IsMoving = true; return true; } else { Debug.LogError($"TCP POST (Move) ½ÇÆÐ"); return false; } } catch (TaskCanceledException) { Debug.Log("TCP ¿äûÀÌ ¿¡·¯·Î ÀÎÇØ Ãë¼Ò."); return false; } catch (Exception e) { Debug.Log($"MoveToPoseTcpAsync ¿À·ù: {e.Message}"); return false; } finally { //clientLock.Release(); } } // TCP POST À̵¿ ¸í·É üũ public async Task StartMovementCheckLoopAsync(CancellationToken token) { if (!isMoveAllowed) { Debug.Log("udpÆÐŶ Àü¼Û Áß´Ü"); return; } while (!token.IsCancellationRequested) { try { if (IsMoving) { await udpClient.SendFilledBytesAsync(new Dictionary { { 2, 0x20 } }); await Task.Delay(10); RobotData currentPose = null; lock (lockObject) { currentPose = this.robotData; } Vector3 targetPos = startMovementPosition; Vector3 currentPos = new Vector3(Convert.ToSingle(currentPose.x), Convert.ToSingle(currentPose.y), Convert.ToSingle(currentPose.z)); if (Vector3.Distance(targetPos, currentPos) < 0.1f) { IsMoving = false; } } else { await Task.Delay(10, token); } } catch (TaskCanceledException) { Debug.Log("MovementCheckLoopAsync Canceled."); } catch (Exception e) { Debug.LogError($"MovementCheckLoopAsync Error: {e.Message}\n{e.StackTrace}"); } } } //Ÿ°Ù Æ÷Áö¼ÇÀÌ ·Îº¿ À̵¿ ¹üÀ§ Ãʰú½Ã ¿¡·¯ ó¸® public async Task GetMovementState(CancellationToken token) { while (!token.IsCancellationRequested) { try { var jsonResponse = await tcpClient.SendGetRequestAsync("/project/rgen"); var pasrsingJsonResponse = HttpResponseParser.ExtractJsonFromHttpResponse(jsonResponse); var tempRobotData = JsonConvert.DeserializeObject(pasrsingJsonResponse, new JsonSerializerSettings { CheckAdditionalContent = false }); jsonResponse = await tcpClient.SendGetRequestAsync($"/logManager/search?cat_p=E&id_min={Convert.ToInt32(tempRobotData.eid_last_err) - 3}&id_max={tempRobotData.eid_last_err}"); pasrsingJsonResponse = HttpResponseParser.ExtractJsonFromHttpResponse(jsonResponse); tempRobotData = JsonConvert.DeserializeObject(pasrsingJsonResponse, new JsonSerializerSettings { CheckAdditionalContent = false }); bool newErrorState = tempRobotData != null && tempRobotData.code != null && (tempRobotData.code.Contains("228") || tempRobotData.code.Contains("6037")); if (this.isError != newErrorState) { this.isError = newErrorState; OnRobotErrorStateChanged?.Invoke(this.isError); //Debug.Log($"[À̵¿¹üÀ§] À̵¿ °¡´É »óÅÂ: {newErrorState}"); } await Task.Delay(10); } catch (System.Exception) { } } } public void SetLocalErrorState(bool state) { this.isError = state; OnRobotErrorStateChanged?.Invoke(this.isError); } // À̵¿ Áß ÆÇ´Ü public async Task GetMovingState() { string jsonResponse = await tcpClient.SendGetRequestAsync("/project/robot/moving_to_pose_manual"); string parsingJsonResponse = HttpResponseParser.ExtractJsonFromHttpResponse(jsonResponse); return parsingJsonResponse; } /// /// ÇÁ·Î±×·¥ Á¤º¸ ÆÐ³Î /// public async Task ResetProgram() { int jobProgramLines = CurrentProgram.Steps.Count + 1; var payload = new { idx_st = 1, n_line = jobProgramLines, save_file = true }; string jsonPayload = JsonConvert.SerializeObject(payload); HttpContent content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); string requestUri = $"{tcpBaseUrl}/project/jobs/{CurrentProgram.ProgramId}/del_cmd_lines"; try { HttpResponseMessage result = await httpClient.PostAsync(requestUri, content); if (result.IsSuccessStatusCode) { // ¼­¹ö »èÁ¦ ¼º°ø ½Ã, ·ÎÄà ij½Ã¿¡¼­µµ ÀüºÎ »èÁ¦ CurrentProgram.ResetSteps(); // ´Ù½Ã end¹® Ãß°¡ object endPayload = CmdLinePayload("end", 0); string endJsonString = JsonConvert.SerializeObject(endPayload); HttpContent endContent = new StringContent(endJsonString, Encoding.UTF8, "application/json"); string endRequestUri = $"{tcpBaseUrl}/project/jobs/{CurrentProgram.ProgramId}/ins_cmd_line"; HttpResponseMessage endResponse = await httpClient.PostAsync(endRequestUri, endContent); if (endResponse.IsSuccessStatusCode) { return true; } else { Debug.LogError($"ResetProgramAsync: 'end' ¸í·É¾î Ãß°¡ ½ÇÆÐ: {await endResponse.Content.ReadAsStringAsync()}"); return false; } } Debug.LogError($"ResetProgramAsync ½ÇÆÐ: {await result.Content.ReadAsStringAsync()}"); return false; } catch (Exception e) { Debug.LogError($"ÇÁ·Î±×·¥ ¸®¼Â ½ÇÆÐ: {e.Message}"); return false; } } public async Task startProgram() { string requestUri = $"{tcpBaseUrl}/project/robot/start"; HttpContent content = new StringContent("{}", Encoding.UTF8, "application/json"); try { HttpResponseMessage result = await httpClient.PostAsync(requestUri, content); if (result.IsSuccessStatusCode) { Debug.Log("ÇÁ·Î±×·¥ Àç»ý"); } } catch (Exception e) { Debug.LogError($"ÇÁ·Î±×·¥ Àç»ý ½ÇÆÐ: {e.Message}"); } } public async Task stopProgram() { string requestUri = $"{tcpBaseUrl}/project/robot/stop"; HttpContent content = new StringContent("{}", Encoding.UTF8, "application/json"); try { HttpResponseMessage result = await httpClient.PostAsync(requestUri, content); if (result.IsSuccessStatusCode) { Debug.Log("ÇÁ·Î±×·¥ Á¤Áö"); } } catch (Exception e) { Debug.LogError($"ÇÁ·Î±×·¥ Á¤Áö ½ÇÆÐ: {e.Message}"); } } public async Task setProgramSpeed(int spdRate) { var payload = new { type = 1, val = spdRate }; string jsonPayload = JsonConvert.SerializeObject(payload); HttpContent content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); string requestUri = $"{tcpBaseUrl}/project/control/op_cnd/set_playback_spd_rate"; try { HttpResponseMessage result = await httpClient.PostAsync(requestUri, content); if (result.IsSuccessStatusCode) { Debug.Log("Àç»ý ¼Óµµ º¯°æ"); } } catch (Exception e) { Debug.LogError($"Àç»ý ¼Óµµ º¯°æ ½ÇÆÐ: {e.Message}"); } } }