using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using Project.StateMachine;
using COMM;
using AR;
using AGVNavigationCore.Utils;
using AGVNavigationCore.PathFinding.Core;
using AGVNavigationCore.Models;
using AGVNavigationCore.PathFinding.Planning;
namespace Project
{
public partial class fMain
{
bool CheckStopCondition()
{
//이머전시상태라면 stop 처리한다.
if (PUB.AGV.error.Emergency &&
PUB.AGV.system1.agv_stop == true &&
PUB.AGV.system1.stop_by_front_detect == false)
{
PUB.Speak(Lang.비상정지로인해작업을중단합니다);
PUB.sm.SetNewStep(eSMStep.IDLE);
return false;
}
//수동충전상태라면 이동하지 못한다
if (VAR.BOOL[eVarBool.FLAG_CHARGEONM])
{
PUB.Speak("수동 충전중이라 사용할 수 없습니다");
PUB.sm.SetNewStep(eSMStep.IDLE);
return false;
}
return true;
}
AGVPathResult CalcPath(MapNode startNode, MapNode targetNode)
{
var nodes = PUB._mapCanvas.Nodes;
var _simulatorCanvas = PUB._mapCanvas;
var _mapNodes = PUB._mapCanvas.Nodes;
// 현재 AGV 방향 가져오기
var selectedAGV = PUB._virtualAGV;
var currentDirection = selectedAGV.CurrentDirection;
// AGV의 이전 위치에서 가장 가까운 노드 찾기
var prevNode = selectedAGV.PrevNode;
var prevDir = selectedAGV.PrevDirection;
// Core Logic으로 이관됨
var pathFinder = new AGVPathfinder(nodes);
var result = pathFinder.CalculatePath(startNode, targetNode, prevNode, prevDir);
//게이트웨이노드를 하이라이트강조 한단
_simulatorCanvas.HighlightNodeId = (result.Gateway?.Id ?? string.Empty);
return result;
}
///
/// 실행스텝을 오류로 전환합니다.
/// 로그메세지(에러)가 추가됩니다.
/// 맵캔버스오류경고메세지가 설정됩니다
///
///
///
public void SetRunStepError(ENIGProtocol.AGVErrorCode ecode, string errmsg = "")
{
if (string.IsNullOrEmpty(errmsg))
{
errmsg = ENIGProtocol.AGVUtility.GetAGVErrorMessage(ecode);
}
PUB.Result.ResultMessage = errmsg;
PUB.XBE.SendError(ecode);
PUB.AGV.AGVMoveStop(errmsg);
PUB.log.AddE(errmsg);
PUB._mapCanvas.SetAlertMessage(errmsg);
PUB.Result.RunStepErrorCode = ecode;
PUB.sm.SetNewRunStep(ERunStep.ERROR);
}
///
/// 라이더멈춤이 설정되어있다면 음성으로 알려준다
///
///
public bool CheckLiderStop()
{
if (PUB.AGV.system1.stop_by_front_detect == true)
{
var tsSpeak = DateTime.Now - LastSpeakTime;
if (tsSpeak.TotalSeconds >= PUB.setting.alarmSoundTerm)
{
PUB.Speak(Lang.전방에물체가감지되었습니다);
LastSpeakTime = DateTime.Now;
}
return false;
}
return true;
}
///
/// 설정된 목적지까지 이동을 완료 한 후 True를 반환합니다.
/// 목적지 : PUB._virtualAGV.TargetNode
///
///
///
///
Boolean UpdateMotionPositionForMark(string sender)
{
////현재위치를 모르는 상태라면 처리하지 않는다
if (PUB._virtualAGV.CurrentNode == null || PUB._virtualAGV.PrevNode == null)
return false;
//현재위치노드 오류
var currentNode = PUB.FindByNodeID(PUB._virtualAGV.CurrentNode.Id);
if (currentNode == null)
{
PUB.log.AddE($"현재위치노드가 없습니다");
PUB.sm.SetNewRunStep(ERunStep.READY);
return false;
}
//시작노드값이 없다면 현재위치를 노드로 결정한다
if (PUB._virtualAGV.StartNode == null)
PUB._virtualAGV.StartNode = PUB.FindByNodeID(PUB._virtualAGV.CurrentNode.Id);
//시작노드가없다면 오류
if (PUB._virtualAGV.StartNode == null)
{
PUB.log.AddE($"경로시작노드가 없습니다");
PUB.sm.SetNewRunStep(ERunStep.READY);
return false;
}
//대상노드가 없다면 오류
if (PUB._virtualAGV.TargetNode == null)
{
PUB.log.AddE($"경로종료노드가 없습니다");
PUB.sm.SetNewRunStep(ERunStep.READY);
return false;
}
//경로 생성(경로정보가 없거나 현재노드가 경로에 없는경우)
if (PUB._virtualAGV.HasPath() == false ||
PUB._virtualAGV.CurrentPath.DetailedPath.Where(t => t.NodeId.Equals(currentNode.Id)).Any() == false)
{
if (PUB.AGV.system1.agv_run)
{
PUB.log.Add($"경로 재생성으로 인해 구동을 멈춥니다");
PUB.AGV.AGVMoveStop("경로재생성");
}
PUB._virtualAGV.StartNode = PUB._virtualAGV.CurrentNode;
var PathResult = CalcPath(currentNode, PUB._virtualAGV.TargetNode);
if (PathResult.Success == false)
{
PUB.log.AddE($"경로가 계산되지 않았습니다");
PUB.sm.SetNewRunStep(ERunStep.READY);
return false;
}
PUB._mapCanvas.CurrentPath = PathResult;
PUB._virtualAGV.SetPath(PathResult);
PUB.log.AddI($"경로생성 {PUB._virtualAGV.StartNode.RfidId} -> {PUB._virtualAGV.TargetNode.RfidId}");
}
//경로에 대한 무결성 검증
if (CheckPathIntegrity(PUB._virtualAGV.CurrentPath) == false)
{
if (PUB.AGV.system1.agv_run)
{
PUB.AGV.AGVMoveStop("Path Integrity Fail");
}
PUB.log.AddE($"경로 무결성 오류로 인해 경로를 삭제 합니다");
PUB._virtualAGV.SetPath(null);
VAR.I32[eVarInt32.PathValidationError] += 1;
if (VAR.I32[eVarInt32.PathValidationError] > 50)
{
SetRunStepError(ENIGProtocol.AGVErrorCode.PATH_INTEGRITY_FAIL, $"연속 경로 무결성 오류로 인해 중지 합니다");
}
return false;
}
else VAR.I32[eVarInt32.PathValidationError] = 0;
//현재위치 기준으로 재 계산하여. 더 최적화된 루트가 있다면 처리를 해준다.
if (PUB._virtualAGV.CurrentPath.DetailedPath.Count > 5)
{
var PathResult2 = CalcPath(PUB._virtualAGV.CurrentNode, PUB._virtualAGV.TargetNode);
if (PathResult2 != null && PathResult2.Success)
{
//절반이상 경로가 짧을때에는 재계산을 하게한다
var halfcnt = (int)(PUB._virtualAGV.CurrentPath.DetailedPath.Count / 2.0);
if (PathResult2.DetailedPath.Count < halfcnt)
{
var msg = $"단축경로가 확인되었습니다. 경로를 삭제 합니다";
PUB.log.AddE(msg);
Console.WriteLine(msg);
PUB._virtualAGV.SetPath(null);
return false;
}
}
}
//predict 를 이용하여 다음 이동을 모두 확인한다.
var nextAction = PUB._virtualAGV.Predict();
// [DEBUG] 예측 결과 로그 추가
// 너무 빈번하게 찍히지 않도록 변화가 있을 때만 찍거나, 특정 조건에서 찍는 것이 좋으나
// 디버깅 요청이므로 일단 주요 정보 출력
// (실제 운용시에는 Verbose 레벨로 조정 필요)
// PUB.log.Add($"[DEBUG] Predict: Reason={nextAction.Reason}, Motor={nextAction.Motor}, Magnet={nextAction.Magnet}, Speed={nextAction.Speed}");
if (nextAction.Reason == AGVNavigationCore.Models.eAGVCommandReason.PathOut)
{
//경로이탈
var logmessage = $"경로이탈감지 시작노드를 현재위치로 설정합니다 START:{PUB._virtualAGV.StartNode},CURRENT:{PUB._virtualAGV.CurrentNode}";
PUB.log.AddE(logmessage);
Console.WriteLine(logmessage);
PUB._virtualAGV.ClearPath();//.DetailedPath.Clear();
PUB._virtualAGV.StartNode = PUB._virtualAGV.CurrentNode;
return false;
}
//모터에서 정지를 요청했다
if (nextAction.Motor == AGVNavigationCore.Models.MotorCommand.Stop)
{
if (PUB.AGV.system1.agv_run)
{
// 완료(Complete) 상태라면 MarkStop 전송
if (nextAction.Reason == AGVNavigationCore.Models.eAGVCommandReason.MarkStop)
{
if (PUB.AGV.data.Speed != 'S')
{
// [쿨타임 적용] 정지 명령은 별도의 타이머(LastStopCommandTime)를 사용하여
// 이동 직후라도 즉시 반응할 수 있도록 한다. (이미 정지신호를 보냈다면 2초 대기)
var tsCmd = VAR.TIME.RUN(eVarTime.LastStopCommandTime);
if (tsCmd.TotalSeconds >= 2.0)
{
PUB.log.Add("다음행동예측에서 MARK STOP이 확인되었습니다 (자동정지시퀀스)");
PUB.AGV.AGVMoveStop(nextAction.Message, arDev.Narumi.eStopOpt.MarkStop);
// 정지 타이머 갱신 (연속 정지 방지)
VAR.TIME.Update(eVarTime.LastStopCommandTime);
// 일반 타이머도 갱신 (정지 직후 불필요한 이동 방지)
LastCommandTime = DateTime.Now;
}
}
}
else
{
// [쿨타임 적용] 정지 명령은 별도의 타이머(LastStopCommandTime)를 사용하여
// 이동 직후라도 즉시 반응할 수 있도록 한다. (이미 정지신호를 보냈다면 2초 대기)
var tsCmd = VAR.TIME.RUN(eVarTime.LastStopCommandTime);
if (tsCmd.TotalSeconds >= 2.0)
{
PUB.log.Add($"다음행동예측에서 장비 멈춤이 확인되었습니다({nextAction.Reason})");
PUB.AGV.AGVMoveStop(nextAction.Message);
// 정지 타이머 갱신 (연속 정지 방지)
VAR.TIME.Update(eVarTime.LastStopCommandTime);
// 일반 타이머도 갱신 (정지 직후 불필요한 이동 방지)
LastCommandTime = DateTime.Now;
}
}
}
// 목적지 도착 여부 확인
// .. (생략) ..
if (PUB._virtualAGV.IsPositionConfirmed)
{
if (PUB.AGV.system1.agv_run == false)
{
// 경로가 존재한다면...
if (PUB._virtualAGV.CurrentPath != null && PUB._virtualAGV.CurrentPath.DetailedPath.Any())
{
var lastInfo = PUB._virtualAGV.CurrentPath.DetailedPath.Last();
// 위치와 방향이 모두 일치해야 완료된 것으로 본다.
if (PUB._virtualAGV.CurrentNode.Id == lastInfo.NodeId &&
PUB._virtualAGV.CurrentDirection == lastInfo.MotorDirection)
{
var node = PUB._mapCanvas.Nodes.Where(t => t.Id == PUB._virtualAGV.CurrentNodeId).FirstOrDefault();
var rfid = node?.ID2 ?? "(X)";
PUB.log.AddI($"목표 도착 및 정지 확인됨(MarkStop 완료) Node:{rfid}, Dir:{PUB._virtualAGV.CurrentDirection}");
return true;
}
else
{
// [DEBUG] 도착했으나 조건 불일치
// PUB.log.Add($"[DEBUG] Arrived but condition mismatch. CurNode:{PUB._virtualAGV.CurrentNode.Id}, Target:{lastInfo.NodeId}, CurDir:{PUB._virtualAGV.CurrentDirection}, TargetDir:{lastInfo.MotorDirection}");
}
}
else
{
// ...
if (PUB._virtualAGV.CurrentNode.Id == PUB._virtualAGV.TargetNode.Id)
{
// ...
PUB.log.AddI($"목표 도착 및 정지 확인됨(MarkStop 완료, No Path Info) Node:...");
return true;
}
}
}
}
return false;
}
else
{
// 이동 명령 변환 (AGVNavigationCore -> arDev.Narumi)
var bunki = arDev.Narumi.eBunki.Strate;
if (nextAction.Magnet == AGVNavigationCore.Models.MagnetPosition.L) bunki = arDev.Narumi.eBunki.Left;
else if (nextAction.Magnet == AGVNavigationCore.Models.MagnetPosition.R) bunki = arDev.Narumi.eBunki.Right;
var dir = arDev.Narumi.eMoveDir.Forward;
if (nextAction.Motor == AGVNavigationCore.Models.MotorCommand.Backward) dir = arDev.Narumi.eMoveDir.Backward;
var spd = arDev.Narumi.eMoveSpd.Low;
if (nextAction.Speed == AGVNavigationCore.Models.SpeedLevel.M) spd = arDev.Narumi.eMoveSpd.Mid;
else if (nextAction.Speed == AGVNavigationCore.Models.SpeedLevel.H) spd = arDev.Narumi.eMoveSpd.High;
// 방향 전환 시 정지 로직 추가
// 이동 중인데 방향이 다르면 먼저 정지시킨다.
if (PUB.AGV.system1.agv_run)
{
if (PUB.AGV.data.Direction != dir.ToString()[0])
{
// 2초 쿨타임 (정지 명령도 빈번한 전송 방지)
var tsCmd = DateTime.Now - LastCommandTime;
if (tsCmd.TotalSeconds >= 2.0)
{
PUB.log.Add($"방향 전환을 위해 정지 명령을 전송합니다 Current:{PUB.AGV.data.Direction} Target:{dir}");
PUB.AGV.AGVMoveStop("Direction Change");
LastCommandTime = DateTime.Now;
}
return false;
}
}
// 명령 설정
// 현재 상태와 다를 때만 전송 (불필요한 통신 부하 방지)
if (PUB.AGV.data.Sts != bunki.ToString()[0] ||
PUB.AGV.data.Direction != dir.ToString()[0] ||
PUB.AGV.data.Speed != spd.ToString()[0])
{
// 2초 쿨타임 적용
var tsCmd = DateTime.Now - LastCommandTime;
if (tsCmd.TotalSeconds >= 2.0)
{
var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
{
Bunki = bunki,
Direction = dir,
PBSSensor = 1,
Speed = spd,
});
if (ret == arDev.eNarumiCommandResult.Success)
PUB.log.Add($"Predict Run Setting = bunki:{bunki},dir:{dir},pbs:1,spd:{spd}");
else
PUB.log.AddE($"Predict Run Setting = bunki:{bunki},dir:{dir},pbs:1,spd:{spd}");
LastCommandTime = DateTime.Now;
}
}
// AGV가 정지 상태라면 구동 시작
if (PUB.AGV.system1.agv_run == false)
{
// 2초 쿨타임 적용 (AGVMoveSet과 동일한 타이머 사용)
var tsCmd = DateTime.Now - LastCommandTime;
if (tsCmd.TotalSeconds >= 2.0)
{
var runOpt = (dir == arDev.Narumi.eMoveDir.Forward) ? arDev.Narumi.eRunOpt.Forward : arDev.Narumi.eRunOpt.Backward;
PUB.AGV.AGVMoveRun(runOpt);
LastCommandTime = DateTime.Now;
}
}
return false;
}
}
///
/// 충전기검색시퀀스
///
///
///
Boolean UpdateMotionPositionForCharger(string sender)
{
if (VAR.BOOL[eVarBool.AGVDIR_BACK] == false)// PUB.flag.get(EFlag.FLAG_DIR_BW) == true)
{
//충전기 검색은 항상 앞으로 검색한다
var tsCmd = DateTime.Now - tm_gocharge_command;
if (tsCmd.TotalMilliseconds >= 1999 &&
PUB.AGV.error.Emergency == false &&
PUB.AGV.system1.agv_run == false)
{
var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
{
Bunki = arDev.Narumi.eBunki.Strate,
Direction = arDev.Narumi.eMoveDir.Forward,
PBSSensor = 1,
Speed = arDev.Narumi.eMoveSpd.Low,
});
if (ret == arDev.eNarumiCommandResult.Success)
{
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);//
}
tm_gocharge_command = DateTime.Now;
}
}
else
{
//현재위치가 충전위치이고, 움직이지 않았다면 완료된 경우라 할수 있따
if (PUB._virtualAGV.CurrentNode.Id.Equals(PUB.setting.NodeMAP_RFID_Charger) &&
VAR.BOOL[eVarBool.MARK_SENSOR] == true)
{
PUB.log.AddI("충전위치 검색 완료");
return true;
}
else
{
//이동중이지 않다면 항상 이동을 해줘야한다
var tsCmd = DateTime.Now - LastCommandTime;
if (tsCmd.TotalMilliseconds >= 1999 &&
PUB.AGV.error.Emergency == false &&
PUB.AGV.system1.agv_run == false)
{
var ret = PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
{
Bunki = arDev.Narumi.eBunki.Strate,
Direction = arDev.Narumi.eMoveDir.Backward,
PBSSensor = 1,
Speed = arDev.Narumi.eMoveSpd.Low,
});
if (ret == arDev.eNarumiCommandResult.Success)
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);//
LastCommandTime = DateTime.Now;
}
}
//}
}
return false;
}
///
/// 경로 무결성(도킹방향 등) 검증
///
///
///
private bool CheckPathIntegrity(AGVNavigationCore.PathFinding.Core.AGVPathResult pathResult)
{
if (pathResult == null) return false;
// CalcPath에서 이미 DockingValidator를 수행했을 수 있음.
// 만약 수행되지 않았다면 여기서 수행.
if (pathResult.DockingValidation == null)
{
pathResult.DockingValidation = AGVNavigationCore.Utils.DockingValidator.ValidateDockingDirection(pathResult, PUB._mapCanvas.Nodes);
}
// 검증 결과 확인
if (pathResult.DockingValidation != null && pathResult.DockingValidation.IsValidationRequired)
{
if (pathResult.DockingValidation.IsValid == false)
{
PUB.log.AddE($"[경로무결성오류] {pathResult.DockingValidation.ValidationError}");
return false;
}
}
return true;
}
}//cvass
}