529 lines
17 KiB
C#
529 lines
17 KiB
C#
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
using Palmmedia.ReportGenerator.Core;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Net.Mail;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using UdpClientLib;
|
|
using UnityEditor.PackageManager;
|
|
using UnityEngine;
|
|
|
|
public interface IProgramModel
|
|
{
|
|
|
|
}
|
|
|
|
public class ProgramModel : IProgramModel
|
|
{
|
|
private string tcpBaseUrl;
|
|
private string udpBaseUrl;
|
|
HttpClient httpClient = new HttpClient();
|
|
private SingleTcpClient tcpClient;
|
|
private SingleUdpClient udpClient;
|
|
public UdpClientManager manager = new UdpClientManager();
|
|
|
|
private List<JobInfoDTO> allProgramsCache = new List<JobInfoDTO>();
|
|
public RobotProgram CurrentProgram { get; private set; }
|
|
private RobotData robotData;
|
|
private RobotController robotController;
|
|
|
|
private readonly object lockObject = new object();
|
|
private bool hasNewData;
|
|
public bool IsMoving;
|
|
public bool isError;
|
|
private Vector3 startMovementPosition;
|
|
private int currentToolNum = 0;
|
|
|
|
public ProgramModel(string hostip, int tcpPort, int udpPort, RobotController robotController)
|
|
{
|
|
tcpBaseUrl = $"http://{hostip}:{tcpPort}";
|
|
udpBaseUrl = $"http://{hostip}:{udpPort}";
|
|
_ = HandleAsyncWork(hostip, tcpPort, udpPort);
|
|
}
|
|
|
|
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 = false;
|
|
IsMoving = false;
|
|
isError = false;
|
|
|
|
try
|
|
{
|
|
currentToolNum = await GetRobotToolNum();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogWarning($"현재 툴 번호를 가져오는 데 실패했습니다: {e.Message}. 기본값(0)을 사용합니다.");
|
|
currentToolNum = 1;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
public bool IsNewDataAvailable()
|
|
{
|
|
lock (lockObject)
|
|
{
|
|
return hasNewData;
|
|
}
|
|
}
|
|
|
|
public RobotData GetLatestRobotData()
|
|
{
|
|
lock (lockObject)
|
|
{
|
|
hasNewData = false; // 데이터를 읽었으므로 플래그를 내림
|
|
return robotData;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 프로그램 생성/불러오기 시스템
|
|
/// </summary>
|
|
|
|
public async Task<bool> 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<bool> 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(userInputId);
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"프로그램 생성 실패: {userInputId}, {e.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private async Task<string> 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<bool> 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<int> 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;
|
|
}
|
|
|
|
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<RobotData>(jsonResponse, new JsonSerializerSettings { CheckAdditionalContent = false });
|
|
|
|
lock (lockObject)
|
|
{
|
|
robotData = tempRobotData;
|
|
hasNewData = true;
|
|
}
|
|
await Task.Delay(50);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.Log(e);
|
|
await Task.Delay(1000); // 에러 시 더 긴 대기
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task<bool> 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;
|
|
}
|
|
|
|
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<JobListWrapper>(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 List<string> GetAllProgramIds()
|
|
{
|
|
List<string> ids = new List<string>();
|
|
foreach (var jobInfo in allProgramsCache)
|
|
{
|
|
ids.Add(jobInfo.fname);
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 로봇 위치 기록 시스템
|
|
/// </summary>
|
|
public async Task<bool> 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 = new
|
|
{
|
|
adjust_branch = true,
|
|
cmd_line = $"move P,spd=50%,accu=3,tool={currentToolNum}",
|
|
fno_ref = -1,
|
|
mechinfo = -1,
|
|
move_cursor = 1,
|
|
ofs = 0,
|
|
save_file = true,
|
|
set_cur_pose = -1,
|
|
sno_ref = 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)
|
|
{
|
|
// 서버 저장이 성공하면, 로컬 캐시에도 반영
|
|
if (index == -1)
|
|
CurrentProgram.AddStep(pointData, currentToolNum); // 로컬 캐시에 추가
|
|
else
|
|
CurrentProgram.UpdateStep(index, pointData); // 로컬 캐시 수정
|
|
|
|
return true;
|
|
}
|
|
|
|
Debug.LogError($"SavePointToProgramAsync 실패: {await result.Content.ReadAsStringAsync()}");
|
|
return false;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"포인트 저장 실패: {e.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 서버에 포인트 삭제 요청
|
|
public async Task<bool> 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)
|
|
{
|
|
// 서버 삭제 성공 시, 로컬 캐시에서도 삭제 및 재정렬
|
|
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<bool> MoveToPoseTcpAsync(Vector3 position)
|
|
{
|
|
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));
|
|
|
|
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\":{robotData.rx}, \"ry\":{robotData.ry}, \"rz\":{robotData.rz}}}}}");
|
|
|
|
if (jsonResponse.Contains("200"))
|
|
{
|
|
Debug.Log("TCP POST (Move) 명령 전송 성공");
|
|
IsMoving = true;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError($"TCP POST (Move) 실패");
|
|
return false;
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.Log(e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// TCP POST 이동 명령 체크
|
|
public async Task StartMovementCheckLoopAsync(CancellationToken token)
|
|
{
|
|
try
|
|
{
|
|
while (!token.IsCancellationRequested)
|
|
{
|
|
if (IsMoving)
|
|
{
|
|
await udpClient.SendFilledBytesAsync(new Dictionary<int, byte> { { 2, 0x20 } });
|
|
await Task.Delay(100);
|
|
|
|
RobotData currentPose = null;
|
|
lock (lockObject) { currentPose = this.robotData; }
|
|
|
|
if (currentPose != null)
|
|
{
|
|
bool isApproximatelyX = Mathf.Approximately(startMovementPosition.x, Convert.ToSingle(Math.Round(currentPose.x, 2)));
|
|
bool isApproximatelyY = Mathf.Approximately(startMovementPosition.y, Convert.ToSingle(Math.Round(currentPose.y, 2)));
|
|
bool isApproximatelyZ = Mathf.Approximately(startMovementPosition.z, Convert.ToSingle(Math.Round(currentPose.z, 2)));
|
|
if (isApproximatelyX && isApproximatelyY && isApproximatelyZ)
|
|
{
|
|
IsMoving = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
await Task.Delay(500, token);
|
|
}
|
|
}
|
|
}
|
|
catch (TaskCanceledException)
|
|
{
|
|
Debug.Log("MovementCheckLoopAsync Canceled.");
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"MovementCheckLoopAsync Error: {e.Message}\n{e.StackTrace}");
|
|
}
|
|
}
|
|
|
|
//private async Task GetMovementState()
|
|
//{
|
|
// while (!cancellationTokenSource.Token.IsCancellationRequested)
|
|
// {
|
|
// try
|
|
// {
|
|
// var jsonResponse = await tcpClient.SendGetRequestAsync("/project/rgen");
|
|
// var pasrsingJsonResponse = HttpResponseParser.ExtractJsonFromHttpResponse(jsonResponse);
|
|
// var tempRobotData = JsonConvert.DeserializeObject<RobotStateData>(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<RobotStateData>(pasrsingJsonResponse, new JsonSerializerSettings { CheckAdditionalContent = false });
|
|
|
|
// isError = tempRobotData != null && tempRobotData.code != null && (tempRobotData.code.Contains("228") || tempRobotData.code.Contains("6037"));
|
|
// Debug.Log(isError);
|
|
// await Task.Delay(100);
|
|
// }
|
|
// catch (System.Exception)
|
|
// {
|
|
// }
|
|
// }
|
|
//}
|
|
|
|
public async Task<string> GetMovingState()
|
|
{
|
|
return await tcpClient.SendGetRequestAsync("/project/robot/moving_to_pose_manual");
|
|
}
|
|
} |