using Aurora.Api; using Hermes; using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; using static AuroraPLC.PLCBase; namespace AuroraLaserScanner { public class ScannerSimul : ScannerObj { static new readonly Logger Log = new Logger("Scanner"); // For storage of each queried data set, the user program must provide (at an address specified by Ptr) an area of PC main memory equal to 528520 bytes. [MarshalAs(UnmanagedType.LPArray, SizeConst = 528520)] uint[] m_coordinationTransformPtr = new uint[528520]; //set_trigger 관련 추가, get_waveform 에서 얻어올 포인터 추가, 최대 크기로 설정 (2^20 = 1,048,576) [MarshalAs(UnmanagedType.LPArray, SizeConst = 1048576)] int[] m_getWaveformPtr = new int[1048576]; private bool _ModulationMode; private ScannerConfig _config; // ----------------------------- // MOTF Simulation state // ----------------------------- public Func PLCReadBit; public Func PLCReadWord; public Func PLCReadDWord; public Func PLCGetAbsDeg; private const int PLC_CURRENT_POS_1_ADDR = 4; public Stopwatch sw = Stopwatch.StartNew(); // encoder가 반환할 값 public volatile int _simEncXCount = 0; //랩핑 각도(0~360 public volatile int _simEncYCount = 0; //누적 각도 public volatile bool LastWaitMissedRange = false; private const int RotaryJigCount = 16; private const double JigPerMsec = 300; private const double DegreePerRev = 360.0; private const double OneCycleMsec = JigPerMsec * RotaryJigCount; // 누적각을 scanner 내부에서 유지 public double _angleAbsDeg = 0.0; private bool _listClosed = false; private bool _list1Busy = false; private bool _list2Busy = false; private eListNo _currentExecutingList = eListNo.NONE; private bool _autoChangeRequested = false; private long _listStartMs = 0; // ← 이게 executionStartTime 역할 private const int MinListTimeMs = 30; // 너무 빠른 완료 방지 private const int MaxListTimeMs = 800; // 너무 느린 완료 방지 private int SimulatedListTimeMs = 250; // 리스트 실행 시간 private const double RecommendWindowDeg = 45.0; private const double RecommendUseRatio = 0.35; // 45도 중 실제 마킹이 차지하는 비율(가볍게) public void EquipmentSimulator() { PullEncoderFromPlcOnce(); UpdateListExecution(); } private int DegToCount(double deg) { double c = deg * _config.MarkingOnTheFlyEncoderResolutionRotate / DegreePerRev; return (int)System.Math.Round(c); } private int DegToCountAbs(double absDeg) { double c = absDeg * _config.MarkingOnTheFlyEncoderResolutionRotate / DegreePerRev; long lc = (long)System.Math.Round(c); return unchecked((int)lc); } private void UpdateListExecution() { if (_currentExecutingList == eListNo.NONE) return; long now = sw.ElapsedMilliseconds; long elapsed = now - _listStartMs; int doneTime = GetEffectiveListTimeMs(); // 최소 현실 가드: elapsed가 음수인 경우 방지 if (elapsed < 0) { _listStartMs = now; return; } if (elapsed >= doneTime) { // 현재 리스트 실행 완료 if (_currentExecutingList == eListNo.LIST1) _list1Busy = false; else _list2Busy = false; if (_autoChangeRequested) { // 반대 리스트 자동 실행 _currentExecutingList = (_currentExecutingList == eListNo.LIST1) ? eListNo.LIST2 : eListNo.LIST1; if (_currentExecutingList == eListNo.LIST1) { _list1Busy = true; _list2Busy = false; } else { _list2Busy = true; _list1Busy = false; } _listStartMs = now; _autoChangeRequested = false; } else { _currentExecutingList = eListNo.NONE; } } } private int GetEffectiveListTimeMs() { int t = SimulatedListTimeMs; // 4800msec / 360 * 45 * ratio int recommendedListRunTime = (int)Math.Round((OneCycleMsec / DegreePerRev) * RecommendWindowDeg * RecommendUseRatio); // recommended가 너무 작거나 0이 되는 경우 방지 if (recommendedListRunTime < MinListTimeMs) recommendedListRunTime = MinListTimeMs; if (t < MinListTimeMs) t = MinListTimeMs; if (t > MaxListTimeMs) t = MaxListTimeMs; if (recommendedListRunTime > MaxListTimeMs) recommendedListRunTime = MaxListTimeMs; return t; } public void TrySyncAbsAngleToPlcNow() { _angleAbsDeg = PLCGetAbsDeg(); // mod 동기화도 같이 int modRaw = PLCReadDWord(PLC_CURRENT_POS_1_ADDR, ePLCDevice.W, false).data; double modDeg = (modRaw / 100_000.0) % DegreePerRev; if (modDeg < 0) modDeg += DegreePerRev; LastWaitMissedRange = false; } public void ResetSim() { _angleAbsDeg = 0.0; _simEncXCount = 0; _simEncYCount = 0; StopExecution(); // 리스트 상태 리셋 LastWaitMissedRange = false; } public override uint Open() { IsOpenUnit = true; return NO_ERROR; } public override void Close() { if (IsOpenUnit == true) { IsOpenUnit = false; } } public override int Initialize(ScannerConfig config) { CommandPosX = 0; CommandPosY = 0; CommandPosZ = 0; _ModulationMode = config.Modulation; _config = config; return E1.OK; } public override bool GetModulationMode() { return _ModulationMode; } public override void JumpAbs(double x, double y, eFunctionType type = eFunctionType.COMMAND) { CommandPosX = x; CommandPosY = y; } public override void JumpAbs3D(double x, double y, double z, eFunctionType type = eFunctionType.COMMAND) { CommandPosX = x; CommandPosY = y; CommandPosZ = z; } public override void JumpRel(double x, double y, eFunctionType type = eFunctionType.COMMAND) { CommandPosX += x; CommandPosY += y; } public override void JumpRel3D(double x, double y, double z, eFunctionType type = eFunctionType.COMMAND) { CommandPosX += x; CommandPosY += y; CommandPosZ += z; } public override void MarkAbs(double x, double y) { CommandPosX = x; CommandPosY = y; } public override void MarkRel(double x, double y) { CommandPosX += x; CommandPosY += y; } public override void ArcAbs(double x, double y, double angle) { CommandPosX = x; CommandPosY = y; } public override void ArcRel(double x, double y, double angle) { CommandPosX += x; CommandPosY += y; } public override void JumpAbsZ(double z, eFunctionType type = eFunctionType.COMMAND) { CommandPosZ = z; } public override void JumpRelZ(double z, eFunctionType type = eFunctionType.COMMAND) { CommandPosZ += z; } public override void GetXYPos(out double x, out double y) { x = CommandPosX; y = CommandPosY; } public override void GetZPos(out double z) { z = CommandPosZ; } public override void WriteDA(uint portNo, float value/*0~100%*/, eFunctionType type = eFunctionType.COMMAND) { } public override void GetEncoder(out double encoderX, out double encoderY, bool rotateMode = false) { int valueX = _simEncXCount; int valueY = _simEncYCount; // 너가 쓰는 변환식 그대로 encoderX = (valueX * DegreePerRev) / _config.MarkingOnTheFlyEncoderResolutionRotate; encoderY = _angleAbsDeg; if (encoderY > -0.5 && encoderY < 0) encoderY = 0; // 시뮬 안정화 return; } public override uint GetMarkingInfo() { //return RTC5Wrap.n_get_marking_info(m_deviceNo); return 0; } public override uint GetStartStopInfo() { //return RTC5Wrap.n_get_startstop_info(m_deviceNo); return 0; } public override void StoreEncoder(uint pos) { //RTC5Wrap.n_store_encoder(m_deviceNo, pos); } public override void ReadEncoder(out double encoder0_0, out double encoder1_0, out double encoder0_1, out double encoder1_1, bool rotateMode = false) { // simulation 할때 값 반영 검토 필요 encoder0_0 = 0; encoder1_0 = 0; encoder0_1 = 0; encoder1_1 = 0; } public override void WaitForEncoderX(double position) { // simulation 할때 값 반영 검토 필요 } public override void WaitForEncoderY(double position) { // simulation 할때 값 반영 검토 필요 } public override void WaitForEncoderRotateX(double angle) { // simulation 할때 값 반영 검토 필요 } public override void WaitForEncoderRotateY(double angle) { // simulation 할때 값 반영 검토 필요 } public override void WaitForEncoderRotateXMode(double angle, eEncoderWaitThroughMode mode) { // simulation 할때 값 반영 검토 필요 } public override void WaitForEncoderRotateYMode(double angle, eEncoderWaitThroughMode mode) { // simulation 할때 값 반영 검토 필요 } public override void WaitForEncoderXMode(double position, eEncoderWaitThroughMode mode) { // simulation 할때 값 반영 검토 필요 } public override void WaitForEncoderYMode(double position, eEncoderWaitThroughMode mode) { // simulation 할때 값 반영 검토 필요 } public override void WaitForEncoderMode(double position, uint encoderNo, eEncoderWaitThroughMode mode) { // simulation 할때 값 반영 검토 필요 } public override void WaitForEncoderRange(double minX, double maxX, double minY, double maxY) { // simulation 할때 값 반영 검토 필요 } public override void WaitForEncoderRotateRange(double minX, double maxX, double minY, double maxY) { LastWaitMissedRange = false; long t0 = sw.ElapsedMilliseconds; while (true) { // 블로킹 대기 중에도 PLC에서 최신값을 직접 당겨온다 PullEncoderFromPlcOnce(); // GetEncoder는 _simEncYCount를 deg로 바꿔줄 것 GetEncoder(out _, out double curY, true); // 정상 진입 if (curY >= minY && curY <= maxY) return; // 최소 현실 가드: 구간을 지나쳤으면 TIMEOUT까지 끌지 말고 즉시 종료 // (실장에서도 "놓치면" 의미 없고 다음 로직이 꼬임) if (curY > maxY) { LastWaitMissedRange = true; Log.W($"[SIM] WaitForEncoderRotateRange MISSED. curY={curY:F3}, range=[{minY:F3}..{maxY:F3}]"); return; } // 최후 안전장치 if (sw.ElapsedMilliseconds - t0 > 5000) { LastWaitMissedRange = true; Log.W($"[SIM] WaitForEncoderRotateRange TIMEOUT. curY={curY:F3}, range=[{minY:F3}..{maxY:F3}]"); return; } Thread.Sleep(1); } } private void PullEncoderFromPlcOnce() { // 방어: delegate 미설정이면 아무 것도 못함 if (PLCReadDWord == null || _config == null || _config.MarkingOnTheFlyEncoderResolutionRotate == 0) return; // 1) 랩핑각 (0~360) int modRaw = PLCReadDWord(PLC_CURRENT_POS_1_ADDR, ePLCDevice.W, true).data; double angleModDeg = (modRaw / 100_000.0) % 360.0; if (angleModDeg < 0) angleModDeg += 360.0; // 2) 누적각 (계속 증가) //int absRaw = PLCReadDWord(PLC_CURRENT_POS_ABS_ADDR, ePLCDevice.W, true).data; //double angleAbsDeg = absRaw / 100_000.0; if (PLCGetAbsDeg != null) { _angleAbsDeg = PLCGetAbsDeg(); } // 3) count로 변환 (n_get_encoder가 반환한다고 가정하는 값) _simEncXCount = DegToCount(angleModDeg); _simEncYCount = DegToCountAbs(_angleAbsDeg); } public override void SetFlyX(double encoderResolution) { // simulation 할때 값 반영 검토 필요 } public override void SetFlyX(double encoderResolution, double diameter) { // simulation 할때 값 반영 검토 필요 } public override void SetFlyY(double encoderResolution) { // simulation 할때 값 반영 검토 필요 } public override void SetRotateCenter(double posX, double posY, eFunctionType type = eFunctionType.COMMAND) { // simulation 할때 값 반영 검토 필요 } public override void SetFlyRotate(double encoderResolution) { // simulation 할때 값 반영 검토 필요 } public override void SetFlyRotatePos(double encoderResolution) { // simulation 할때 값 반영 검토 필요 } public override void ActivateFlyXY(double encoderResolutionX, double encoderResolutionY) { // simulation 할때 값 반영 검토 필요 } public override void ActivateFly1Axis(int axis, int mode, double scale, int offset) { } public override void Fly_Return(double x, double y) { // simulation 할때 값 반영 검토 필요 } public override void ParkPosition(double posX, double posY, eParkType type = eParkType.VirtualImage) { // simulation 할때 값 반영 검토 필요 } public override void ParkReturn(double posX, double posY, eParkType type = eParkType.VirtualImage) { // simulation 할때 값 반영 검토 필요 } public override void InitFly2D(int offsetX = 0, int offsetY = 0) { // simulation 할때 값 반영 검토 필요 } public override void SimulateEncoder(uint EncoderNo) { // simulation 할때 값 반영 검토 필요 } public override void SetControlMode(uint mode) { // simulation 할때 값 반영 검토 필요 } public override void SetLaesrDelay(int laserOnDelay, uint laserOffDelay) { // simulation 할때 값 반영 검토 필요 } public override void SetScannerDelay(uint jumpDelay, uint markDelay, uint arcDelay) { // simulation 할때 값 반영 검토 필요 } public override void SetJumpSpeedCtrl(double speed) { // simulation 할때 값 반영 검토 필요 } public override void SetJumpSpeed(double speed) { // simulation 할때 값 반영 검토 필요 } public override void SetMarkSpeed(double speed) { // simulation 할때 값 반영 검토 필요 } public override void SetStartList(eListNo no) { // simulation 할때 값 반영 검토 필요 } public override uint LoadList(eListNo no) { bool busy = (no == eListNo.LIST1) ? _list1Busy : _list2Busy; if (busy) return 0; // 실행 중 → Load 불가 return (uint)no; // Load 가능 } public override void SetEndList() { _listClosed = true; } public override void ExecuteList(eListNo no) { if (!_listClosed) Log.W("ExecuteList called without SetEndList"); _listClosed = false; // 실행 시작하면서 다시 열림 상태로 _currentExecutingList = no; if (no == eListNo.LIST1) { _list1Busy = true; _list2Busy = false; } else { _list2Busy = true; _list1Busy = false; } _listStartMs = sw.ElapsedMilliseconds; } public override void StartLoop() { RTC5Wrap.n_start_loop(m_deviceNo); } public override void QuitLoop() { // simulation 할때 값 반영 검토 필요 } public override void AutoChange() { if (_currentExecutingList == eListNo.NONE) return; _autoChangeRequested = true; } public override bool GetBusyStatus() { return _list1Busy || _list2Busy || _currentExecutingList != eListNo.NONE; } public override void StopExecution() { _list1Busy = false; _list2Busy = false; _currentExecutingList = eListNo.NONE; _autoChangeRequested = false; _listStartMs = 0; _listClosed = false; _angleAbsDeg = 0.0; _simEncXCount = 0; _simEncYCount = 0; } public override uint GetRTCVersion() { // simulation 할때 값 반영 검토 필요 return 0; } public override uint GetDLLVersion() { // simulation 할때 값 반영 검토 필요 return 0; } public override void SetLaserTiming(uint periodHz, uint laser1PulseWidthUs, uint laser2PulseWidthUs) //단위는 Khz, us임 { // simulation 할때 값 반영 검토 필요 } public override void SetStandby(uint periodHz, uint laserPulseWidthUs) //단위는 Khz, us임 { // simulation 할때 값 반영 검토 필요 } public override void SetLaserMode(eLaserMode mode) { // simulation 할때 값 반영 검토 필요 } public override void SetLaserControl(uint ctrl) { // simulation 할때 값 반영 검토 필요 } public override void SetFirstPulseKiller(uint width) //단위는 us->bit 로 변경해야 함 { // simulation 할때 값 반영 검토 필요 } public override void LaserSignalOn() { // simulation 할때 값 반영 검토 필요 } public override void LaserSignalOff() { // simulation 할때 값 반영 검토 필요 } /// /// 지정한 시간 시간만큼 Laser Active 신호를 켜주는 리스트 함수 /// /// 시간[ms] public override void LaserOnList(double interval) { // simulation 할때 값 반영 검토 필요 } /// /// Laser Active가 Off된 상태에서 지정한 시간 시간만큼 delay를 추가하는 함수 /// /// 시간[ms] public override void LongDelay(uint delay) { // simulation 할때 값 반영 검토 필요 } public override void SetAngle(double angle/*degree*/, uint atOnce, eFunctionType type = eFunctionType.COMMAND) { // simulation 할때 값 반영 검토 필요 } public override void SetOffset(double offsetX, double offsetY, uint atOnce, eFunctionType type = eFunctionType.COMMAND) { // simulation 할때 값 반영 검토 필요 } public override void DisableLaser() { // simulation 할때 값 반영 검토 필요 } public override void EnableLaser() { // simulation 할때 값 반영 검토 필요 } public override void ParaLaserOnPulsesList(double period, uint pulses, float powerValue) { // simulation 할때 값 반영 검토 필요 } //Ramping 기능 public override void ParaMarkAbs(double x, double y, float power) { CommandPosX = x; CommandPosY = y; // simulation 할때 값 반영 검토 필요 } public override void ParaMarkRel(double x, double y, float power) { CommandPosX += x; CommandPosY += y; // simulation 할때 값 반영 검토 필요 } public override void ParaJumpAbs(double x, double y, float power) { CommandPosX = x; CommandPosY = y; // simulation 할때 값 반영 검토 필요 } public override void ParaJumpRel(double x, double y, float power) { CommandPosX += x; CommandPosY += y; // simulation 할때 값 반영 검토 필요 } public override void SetVectorControl(uint portNo, uint value) { // simulation 할때 값 반영 검토 필요 } //측정 기능 public override void StopTrigger() { // simulation 할때 값 반영 검토 필요 } public override void SetTrigger(uint period/*㎲*/, eRecordSignal signal1, eRecordSignal signal2, eRecordSignal signal3, eRecordSignal signal4) { // simulation 할때 값 반영 검토 필요 } public override void SetTrigger(uint period/*㎲*/, eRecordSignal signal1, eRecordSignal signal2) { // simulation 할때 값 반영 검토 필요 } public override void Measurement_status(out uint busy, out uint pos) { // simulation 할때 값 반영 검토 필요 busy = 0; pos = 0; } public override uint ReadStatus() { // simulation 할때 값 반영 검토 필요 return 0; } public override void GetWaveForm(uint channel, uint number, out int[] data) { data = new int[number]; // simulation 할때 값 반영 검토 필요 } //안오산-170224-Wobbel모드 추가(V1.7.0) //set_wobbel_mode public override void SetWobbelMode(bool wobbelEnable, double transversal, double longitudinal, double frequency, int shape) { // simulation 할때 값 반영 검토 필요 } public override void SetWobbelDirection(int dX, int dY) { // simulation 할때 값 반영 검토 필요 } public override void SetWobbelVector(double tansversal, double longitudinal, uint period, float power) { // simulation 할때 값 반영 검토 필요 } public override void ConfigLaserSignal(eLaserSignal pin2, eLaserSignal pin1, eLaserSignal pin9) { // simulation 할때 값 반영 검토 필요 } public override void SelectCorrectionTable(int no) { // simulation 할때 값 반영 검토 필요 } public override void SetEthernetConfigLinkLoss(eEthConfigLinkLossMode mode) { } public override int CheckEthernetConnection(out uint errorCode) { errorCode = 0; return 0; } #region 리스트 실행 시간 측정 /// /// Delayed Short List Command /// Stores the current value of the RTC6 timer and resets it to zero. /// public override void SaveAndRestartTimer() { // simulation 할때 값 반영 검토 필요 } /// /// Ctrl Command /// Returns the RTC5 timer value (without resetting it to zero). It has been stored during the most recent call of save_and_restart_timer. /// /// RTC5 timer value in seconds. As a 64-bit IEEE floating point value. public override double GetTime() { // simulation 할때 값 반영 검토 필요 return 0; } #endregion // 기존 추상함수 아닌 3개 항목 추상함수로 추가 //ExecuteUpdateParameterList는 Initialize에 직접구현할수밖에.. config의 항목 쓰려면.. public override uint LoadProgramFile(string fileName) { // simulation 할때 값 반영 검토 필요 return 0; } public override uint LoadCorrectionFile(string fileName, uint correctionNo, bool use3DMode) { // simulation 할때 값 반영 검토 필요 return 0; } public static int CutValue(int value, int min, int max) { if (value > max) { return max; } else if (value < min) { return min; } else { return value; } } public override void SetLaserPinOutList(uint pins) { } public override void SetPortDefault(uint port, uint value) { } public override void SetLaserPinOut(uint pins) { } public override uint GetLastError() { return 0; } public override uint GetListSpace() { return 0; } public override void SetConfigList(uint mem1, uint mem2) { } } }