using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading; using GSG.NET.Concurrent; using GSG.NET.LINQ; using GSG.NET.Logging; using GSG.NET.Quartz; using GSG.NET.Utils; using OHV.Common.Events; using OHV.Common.Model; using OHV.Common.Shareds; using OHV.SqliteDAL; using Prism.Events; using VehicleControlSystem.ControlLayer.Actuator.Cylinder; using VehicleControlSystem.ControlLayer.IO; using VehicleControlSystem.ControlLayer.Motion; using VehicleControlSystem.Managers; namespace VehicleControlSystem.ControlLayer { /// /// Control Layer 의 자원을 여기서 사용하자. /// public class Vehicle : ControlObjectBase, IDisposable { /// /// OCS Report Code /// 목적지에 도착해서 Load, Unload 시 발생하는 Alarm /// public enum eFailCode { Load_PortHasNotCarrier = 1, Load_VehicleHasCarrier, Unload_PortHasCarrier, Unload_VehicleHasNotCarrier, LoadPIOInterlockTimeout, UnlaodPIOInterlockTimeout, } static Logger logger = Logger.GetLogger(); static Logger loggerPIO = Logger.GetLogger( "PIO" ); #region Properties private double currentPosition; /// /// Tag 위치 /// public double CurrentPosition { get { return currentPosition; } set { if ( this.currentPosition == value ) return; this.currentPosition = value; this.OnCurrentPotisionChanged?.Invoke( (int)value ); } } //이동 public bool Busy { get { return this.CurrentSubCommand == null ? false : true; } private set { } } public bool IsMoving { get; set; } public double BatteryVolt { get; set; } public bool IsError { get; set; } public bool IsContain { get { return this.IsDetectedCenter(); } } public SubCmd CurrentSubCommand { get; private set; } #endregion #region Event public event Action OnMoveReady; public event Action OnMoving; public event Action OnMoveFinish; public event Action OnChargingStart; public event Action OnCharging; public event Action OnChargingFull; public event Action OnBatteryVelueChanged; public event Action OnPIOStart; public event Action OnConveyorStart; public event Action OnConveyorStop; public event Action OnCarrierDetected; public event Action OnLoadComplete; public event Action OnUnloadComplete; public event Action OnCurrentPotisionChanged; public event Action OnManualMove; public event Action OnManualLoad; public event Action OnManualUnload; public event Action OnManualCharging; public event Action OnFailReport; #endregion IIO iO = null; GSIMotion motion = null; SqliteManager sql = null; Clamp clamp = null; Steering steering = null; AutoManager autoManager = null; #region List. List cylinders = new List(); List obstacleBitList = new List(); #endregion ThreadCancel cancel = new ThreadCancel(); private eObstacleState obstacleState = eObstacleState.Normal; public eObstacleState ObstacleStateProperty { get { return obstacleState; } set { SetField( ref this.obstacleState, value ); } } private eVehicleState vehicleState; public eVehicleState VehicleStateProperty { get { return vehicleState; } set { SetField( ref this.vehicleState, value ); } } IEventAggregator eventAggregator; public Vehicle( IIO io, SqliteManager sqliteManager, IEventAggregator ea, AutoManager auto ) { this.iO = io; this.motion = new GSIMotion(); this.sql = sqliteManager; this.autoManager = auto; this.obstacleBitList.AddRange( new string[] { "OUT_OBSTRUCTION_PATTERN_00", "OUT_OBSTRUCTION_PATTERN_01", "OUT_OBSTRUCTION_PATTERN_02", "OUT_OBSTRUCTION_PATTERN_03", "OUT_OBSTRUCTION_PATTERN_04", } ); this.eventAggregator = ea; this.eventAggregator.GetEvent().Unsubscribe( ReceiveDriveControlEvent ); this.eventAggregator.GetEvent().Subscribe( ReceiveDriveControlEvent ); } private void ReceiveDriveControlEvent( DriveControlEventArgs obj ) { if ( this.autoManager.OperationModeProperty != eOperatationMode.ManualMode ) return; var msg = obj; if ( msg.EventDir == DriveControlEventArgs.eEventDir.ToBack ) { switch ( msg.ControlKind ) { case DriveControlEventArgs.eControlKind.MOVE: break; case DriveControlEventArgs.eControlKind.STOP: break; case DriveControlEventArgs.eControlKind.Steering: if ( msg.MoveDir == DriveControlEventArgs.eMoveDir.LEFT ) this.steering.ControlSteering( true ); else this.steering.ControlSteering(); break; case DriveControlEventArgs.eControlKind.SteeringState: { DriveControlEventArgs reply = new DriveControlEventArgs(); reply.ControlKind = DriveControlEventArgs.eControlKind.SteeringState; if ( this.steering.IsLeft() ) reply.Result = FluentResults.Results.Ok( DriveControlEventArgs.eMoveDir.LEFT ); else reply.Result = FluentResults.Results.Ok( DriveControlEventArgs.eMoveDir.RIGHT ); this.DriveControlEventPublish( reply ); } break; default: break; } } } private void DriveControlEventPublish( DriveControlEventArgs args ) { args.EventDir = DriveControlEventArgs.eEventDir.ToFront; this.eventAggregator.GetEvent().Publish( args ); } public void Init() { this.CreateClamp(); this.CreateSteering(); ThreadStart(); } public int InitializationVehicle() { int result = 0; if ( this.IsDetectedCenter() ) //자제가 있으면 Lock result = this.clamp.Lock_Sync(); else result = this.clamp.Unlock_Sync(); if ( this.motion.IsErrorOn ) return 22; return result; } public void Dispose() { this.cancel.Cancel(); this.cancel.StopWaitAll(); } #region Thread void ThreadStart() { this.cancel.AddGo( new Action( this._ThSubCmdWorker ) ); this.cancel.AddGo( new Action( this._ThObstacleChecker ) ); } //장애물 감지 Thread //장애물 감지 패턴 변경도 여기 하자. private void _ThObstacleChecker() { while ( !this.cancel.Canceled ) { try { if ( this.autoManager.OperationModeProperty == eOperatationMode.AutoMode ) this.CheckObstacle(); } catch ( ThreadInterruptedException threadInterruptedException ) { } catch ( Exception exception ) { logger.E( exception ); } finally { LockUtils.Wait( 5 ); } } logger.D( "Vehicle - _ThObstacleChecker Dispose" ); } /// /// Scheduler 가 주는 Sub Command 를 이용하여 동작하자. /// public void _ThSubCmdWorker() { while ( !this.cancel.Canceled ) { try { if ( this.ObstacleStateProperty != eObstacleState.Normal ) //장애물 감지 상태 시 조그 동작만 가능하게. continue; if ( this.autoManager.AutoModeStateProperty != eAutoModeState.Run ) // continue; var subCmd = sql.SubCmdDAL.GetSubCmd(); if ( subCmd == null ) continue; if ( !sql.CommandDAL.All.Any( x => x.CommandID.Equals( subCmd.CmdID ) ) ) { if ( subCmd.CmdType == SubCmd.eCmdType.Auto ) //자동 명령중 Main Command 가 없으면 삭제. { sql.SubCmdDAL.Delete( subCmd ); logger.I( $"SubCmd Deleted - ID={subCmd.ID}, CommandID={subCmd.CmdID}" ); } } switch ( subCmd.Type ) { case SubCmd.eType.Move: this.CurrentSubCommand = subCmd; this.Move( subCmd ); break; case SubCmd.eType.Load: this.CurrentSubCommand = subCmd; this.LoadCarrier( subCmd ); break; case SubCmd.eType.Unload: this.CurrentSubCommand = subCmd; this.UnloadCarrier( subCmd ); break; case SubCmd.eType.Charge: this.CurrentSubCommand = subCmd; this.BatteryCharge( subCmd ); break; default: break; } } catch ( ThreadInterruptedException threadInterruptedException ) { } catch ( Exception exception ) { logger.E( exception ); } finally { LockUtils.Wait( 500 ); } } logger.D( "Vehicle - _ThSubCmdWorker Dispose" ); } #endregion #region Control Action Method void Move( SubCmd sub ) { if ( this.MoveTo( sub.TargetID ) ) { sql.SubCmdDAL.Delete( sub ); } else { if ( this.ObstacleStateProperty == eObstacleState.Blocked ) { } } } bool MoveTo( string pointID ) { //this.BuzzerOnOff(true, eBuzzerKind.StartWarn); ////TimerUtils.Once(3000, BuzzerOnOff, false, eBuzzerKind.StartWarn ); //Thread.Sleep(3000); //this.BuzzerOnOff(false); this.OnMoveReady?.Invoke(); var moveReadyBuzzerTime = sql.ConfigDal.GetValueToInt( ConstString.BuzzerStartReadyTime ); Thread.Sleep( moveReadyBuzzerTime ); this.OnMoving?.Invoke(); this.IsMoving = true; //this.BuzzerOnOff(true, eBuzzerKind.Moving); this.motion.MoveToPoint( pointID, 100 ); bool result = Wait4MoveDone(); this.IsMoving = false; //this.BuzzerOnOff(false); this.OnMoveFinish?.Invoke(); return result; } bool Wait4MoveDone() { int waitTime = 6000; //설정 할 수있게. long st = SwUtils.CurrentTimeMillis; //Todo: 이동시 확인 사항들. while ( true ) { Thread.Sleep( 5 ); if ( SwUtils.Gt( st, waitTime ) ) { //Todo: 이동시간 초과 시 동작들. break; } if ( this.ObstacleStateProperty == eObstacleState.Blocked ) return false; } return true; } public bool LoadCarrier( SubCmd sub ) { var route = sql.RouteDal.GetRoute( sub.TargetID ); if ( !CorrectPosition( route, this.CurrentPosition ) ) { this.autoManager.ProcessAlarm( 20 ); return false; //Alarm } int result = this.clamp.Unlock_Sync(); if ( result != 0 ) { this.autoManager.ProcessAlarm( result ); return false; } result = this.PIOAndLoad( sub.TargetID ); if ( result != 0 ) { this.autoManager.ProcessAlarm( result ); return false; } result = this.clamp.Lock_Sync(); if ( result != 0 ) { this.autoManager.ProcessAlarm( result ); return false; } sql.CommandDAL.UpdateState( sub.CmdID, eCommandState.Complete ); sql.SubCmdDAL.Delete( sub ); return true; } public bool UnloadCarrier( SubCmd sub ) { var route = sql.RouteDal.GetRoute( sub.TargetID ); if ( !CorrectPosition( route, this.CurrentPosition ) ) { this.autoManager.ProcessAlarm( 21 ); return false; //Alarm } int result = this.clamp.Unlock_Sync(); if ( result != 0 ) { this.autoManager.ProcessAlarm( result ); return false; } result = this.PIOAndUnload( sub.TargetID ); if ( result != 0 ) { this.autoManager.ProcessAlarm( result ); return false; } sql.CommandDAL.UpdateState( sub.CmdID, eCommandState.Complete ); sql.SubCmdDAL.Delete( sub ); return true; } public bool BatteryCharge( SubCmd sub ) { return true; } #endregion #region Check Method bool CheckObstacle() { if ( this.iO.IsOn( "IN_OBSTRUCTION_DETECT_SAFETY" ) || this.iO.IsOn( "IN_OBSTRUCTION_DETECT_ERROR" ) ) { this.motion.Stop(); this.ObstacleStateProperty = eObstacleState.Abnormal; return true; } if ( this.iO.IsOn( "IN_OBSTRUCTION_DETECT_STOP" ) ) { this.motion.Stop(); this.ObstacleStateProperty = eObstacleState.Blocked; return true; } if ( this.iO.IsOn( "IN_OBSTRUCTION_DETECT_SLOW" ) ) { this.motion.SlowStop(); this.ObstacleStateProperty = eObstacleState.Decelerate; return true; } this.ObstacleStateProperty = eObstacleState.Normal; return false; } #endregion #region Machanical Method #region Conveyor int OnOffConveyor( bool isOn, bool isCW = false ) { if ( IsInverterError() ) return 16; if ( isCW ) this.iO.OutputOn( "OUT_CV_CWCCW" ); else this.iO.OutputOff( "OUT_CV_CWCCW" ); if ( isOn ) this.iO.OutputOn( "OUT_CV_RUN" ); else this.iO.OutputOff( "OUT_CV_RUN" ); return 0; } void SetConveyorSpeed( bool IsHight ) { if ( IsHight ) this.iO.WriteOutputIO( "OUT_CV_DA", true ); else this.iO.WriteOutputIO( "OUT_CV_DA", false ); } /// /// 입구 감지 로딩시 감속 사용 /// /// bool IsDetectedLoadStart() => this.iO.IsOn( "IN_CV_DETECT_00" ); /// /// 실물 감지 /// /// public bool IsDetectedCenter() => this.iO.IsOn( "IN_CV_DETECT_01" ); bool IsDetectedLoadStop() => this.iO.IsOn( "IN_CV_DETECT_02" ); bool IsInverterError() => this.iO.IsOn( "IN_CV_ERROR" ); bool IsLifterPositinCheck() => this.iO.IsOn( "IN_LIFTER_POSITION_DETECT" ); bool IsLifterDuplication() => this.iO.IsOn( "IN_LIFTER_DUPLICATION_DETECT" ); bool IsPIOInterLockOn() => this.iO.IsOn( "OUT_PIO_INTERLOCK" ); int Load_Carrier() { if ( IsDetectedCenter() ) return 9; OnOffConveyor( true, true ); long sTime = SwUtils.CurrentTimeMillis; while ( true ) { if ( SwUtils.Gt( sTime, 20 * ConstUtils.ONE_SECOND ) ) //Wait 20Sec { OnOffConveyor( false, true ); return 10; } if ( IsDetectedLoadStart() ) break; } return 0; } int UnloadCarrier() { if ( !IsDetectedLoadStart() ) return 11; OnOffConveyor( true, true ); long sTime = SwUtils.CurrentTimeMillis; while ( true ) { if ( SwUtils.Gt( sTime, 20 * ConstUtils.ONE_SECOND ) ) //Wait 20Sec { OnOffConveyor( false, true ); return 12; } if ( !IsDetectedLoadStart() ) break; } return 0; } public int PIOAndLoad( string targetName ) { #if SIMULATION PIOClear(); loggerPIO.I($"Start Load PIO - [{targetName}]"); this.OnPIOStart?.Invoke(true); this.iO.WriteOutputIO("OUT_PIO_RECEIVE_RUN", true); loggerPIO.I("[Vehicle] - 4 Receive Run On"); Thread.Sleep(1000);//상대 IO 기다린다 생각. loggerPIO.E("[Port] - 4 Ready On"); //Conveyor Start loggerPIO.I("[Vehicle] - Conveyor Run"); this.OnConveyorStart?.Invoke(true); Thread.Sleep(10000);//Conveyor 구동 this.OnCarrierDetected?.Invoke(true); PIOClear(); Thread.Sleep(1000); this.OnConveyorStop?.Invoke(true); #else var pioTimeout = sql.ConfigDal.GetValueToInt( ConstString.PIOTimeOut ); if ( this.IsInverterError() ) return 16; if ( this.IsLifterPositinCheck() ) return 14; if ( !this.IsLifterDuplication() ) { this.OnFailReport?.Invoke( eFailCode.Load_PortHasNotCarrier ); return 0; } if ( this.IsDetectedCenter() ) { this.OnFailReport?.Invoke( eFailCode.Load_VehicleHasCarrier ); return 0; } PIOClear(); loggerPIO.I( $"Start Load PIO - [{targetName}]" ); this.OnPIOStart?.Invoke( true ); this.iO.WriteOutputIO( "OUT_PIO_RECEIVE_RUN", true ); loggerPIO.I( "[Vehicle] - 4 Receive Run On" ); if ( !this.iO.WaitChangeInputIO( true, pioTimeout, "IN_PIO_SENDABLE" ) ) { PIOClear(); loggerPIO.E( "[Port] - 4 Ready Time Out" ); this.OnFailReport?.Invoke( eFailCode.LoadPIOInterlockTimeout ); return 0; } loggerPIO.E( "[Port] - 4 Ready On" ); this.SetConveyorSpeed( true ); this.OnOffConveyor( true, true ); this.iO.WriteOutputIO( "OUT_PIO_RECEIVE_RUN", true ); loggerPIO.I( "[Vehicle] - Conveyor Run" ); this.OnConveyorStart?.Invoke( true ); if ( !this.iO.WaitChangeInputIO( true, pioTimeout, "IN_PIO_SEND_RUN" ) ) { this.OnOffConveyor( false, true ); PIOClear(); loggerPIO.E( "[Port] - 5 Sending Run Time Out" ); this.OnFailReport?.Invoke( eFailCode.LoadPIOInterlockTimeout ); return 0; } bool isStartDetected = false; var sTime = SwUtils.CurrentTimeMillis; while ( true ) { if ( SwUtils.Gt( sTime, 20 * ConstUtils.ONE_SECOND ) ) { PIOClear(); this.OnOffConveyor( false, true ); loggerPIO.E( "[Vehicle] Conveyor Wait Time Out" ); this.OnFailReport?.Invoke( eFailCode.LoadPIOInterlockTimeout ); if ( this.IsDetectedLoadStart() ) // 감지가 시작 되었으면 이동중 Error 로 판단 설비를 정지 상태로 return 10; //Conveyor Moving Timeout else return 0; } if ( this.IsDetectedLoadStart() && !isStartDetected ) isStartDetected = true; if ( !this.IsDetectedLoadStart() && isStartDetected ) this.SetConveyorSpeed( false ); if ( this.IsDetectedLoadStop() ) break; if ( this.IsPIOInterLockOn() ) { PIOClear(); this.OnOffConveyor( false ); //Stop loggerPIO.E( "[Port] PIO InterLock On " ); return 19; // } } if ( this.IsDetectedCenter() ) this.OnCarrierDetected?.Invoke( true ); this.OnOffConveyor( false ); //Stop PIOClear(); this.OnConveyorStop?.Invoke( true ); this.iO.WriteOutputIO( "OUT_PIO_RECIVE_COMPLITE", true ); this.iO.WriteOutputIO( "OUT_PIO_RECIVE_COMPLITE", false, 1000 ); this.OnLoadComplete?.Invoke(); #endif return 0; } public int PIOAndUnload( string targetName ) { #if SIMULATION PIOClear(); loggerPIO.I($"Start Unload PIO - [{targetName}]"); this.OnPIOStart?.Invoke(false); Thread.Sleep(1000); this.iO.WriteOutputIO("OUT_PIO_READY", true); loggerPIO.I("[Vehicle] - 1 Ready On"); Thread.Sleep(1000); this.OnConveyorStart?.Invoke(false); Thread.Sleep(10000); this.OnOffConveyor(false); //Stop this.OnConveyorStop?.Invoke(false); PIOClear(); this.OnUnloadComplete?.Invoke(); #else var pioTimeout = sql.ConfigDal.GetValueToInt( ConstString.PIOTimeOut ); if ( this.IsInverterError() ) return 16; if ( this.IsLifterDuplication() ) { this.OnFailReport?.Invoke( eFailCode.Unload_PortHasCarrier ); return 0; } if ( !this.IsDetectedCenter() ) { this.OnFailReport?.Invoke( eFailCode.Unload_VehicleHasNotCarrier ); return 0; } if ( this.IsLifterPositinCheck() ) return 13; PIOClear(); loggerPIO.I( $"Start Unload PIO - [{targetName}]" ); this.OnPIOStart?.Invoke( false ); if ( !this.iO.IsOn( "IN_PIO_READY" ) ) { loggerPIO.E( "[Port] - 1 Ready not On" ); this.OnFailReport?.Invoke( eFailCode.UnlaodPIOInterlockTimeout ); return 0; } this.iO.WriteOutputIO( "OUT_PIO_READY", true ); loggerPIO.I( "[Vehicle] - 1 Ready On" ); if ( !this.iO.WaitChangeInputIO( true, pioTimeout, "IN_PIO_RECEIVE_RUN" ) ) { PIOClear(); loggerPIO.E( "[Port] - 2 Receive CV Run Timeout" ); this.OnFailReport?.Invoke( eFailCode.UnlaodPIOInterlockTimeout ); return 0; } this.iO.WriteOutputIO( "OUT_PIO_SENDING_RUN", true ); loggerPIO.I( "[Vehicle] - 2 Send Run On" ); this.SetConveyorSpeed( true ); this.OnOffConveyor( true ); this.OnConveyorStart?.Invoke( false ); var sTime = SwUtils.CurrentTimeMillis; while ( true ) { if ( SwUtils.Gt( sTime, 20 * ConstUtils.ONE_SECOND ) ) { PIOClear(); this.OnOffConveyor( false, true ); loggerPIO.E( "[Port] Conveyor Wait Time Out" ); this.OnFailReport?.Invoke( eFailCode.UnlaodPIOInterlockTimeout ); if ( IsDetectedLoadStart() || IsDetectedCenter() ) //중간에 걸려 있다고 생각해서 알람 처리. return 12; //Conveyor Moving Timeout else return 0; } if ( this.iO.IsOn( "IN_PIO_RECEIVE_COMPLITE" ) ) break; } if ( !IsDetectedCenter() ) this.OnCarrierDetected?.Invoke( false ); this.OnOffConveyor( false ); //Stop this.OnConveyorStop?.Invoke( false ); PIOClear(); this.iO.WriteOutputIO( "OUT_PIO_SEND_COMPLITE", true ); this.iO.WriteOutputIO( "OUT_PIO_SEND_COMPLITE", false, 1000 ); this.OnUnloadComplete?.Invoke(); #endif return 0; } void PIOClear() { string[] pio = { "OUT_PIO_READY", "OUT_PIO_SENDING_RUN", "OUT_PIO_SEND_COMPLITE", "OUT_PIO_RECEIVABLE", "OUT_PIO_RECEIVE_RUN", "OUT_PIO_RECIVE_COMPLITE", "OUT_PIO_INTERLOCK" }; pio.FwEach( x => { this.iO.OutputOff( x ); } ); } #endregion #endregion #region Hardware Create Method void CreateSteering() { this.steering = new Steering( this.iO, this.sql, this.eventAggregator ); this.steering.OnSteeringError += Steering_OnSteeringError; } void CreateClamp() { this.clamp = new Clamp( this.sql, this.eventAggregator ); this.clamp.Init(); } #endregion #region Help Method /// /// 현재 좌표 값이 등록된 Route 에 맞는 위치인지 확인한다. /// 판단 기준은 Route 에 Tolerance 범위를 사용. /// /// /// /// bool CorrectPosition( Route route, double currentPosition ) { var rScale = route.ScaleValue; var rTolerance = route.ScaleTolerance; var result = currentPosition - rScale; if ( rTolerance < Math.Abs( result ) ) return false; return true; } /// /// if no is zero, Laser Off /// bit Off, On, On, On,On Area1 /// /// 0 == Off Laser /// bool ChgObstacleDetectPattern( int no ) { var bitArray = BitUtils.ChgBitArray( no ); int bitIndex = 0; this.obstacleBitList.ForEach( b => { if ( bitArray[bitIndex] ) this.iO.OutputOff( b ); else this.iO.OutputOn( b ); bitIndex++; } ); return true; } int GetObstacleDetectPattern() { int bitIndex = 0; BitArray bitArray = new BitArray( this.obstacleBitList.Count ); this.obstacleBitList.ForEach( b => { if ( this.iO.IsOn( b ) ) bitArray.Set( bitIndex, false ); else bitArray.Set( bitIndex, true ); bitIndex++; } ); return BitUtils.ChgInt32( bitArray ); } #endregion #region Event Subscribe private void Steering_OnSteeringError( object sender, int e ) { if ( e != 0 ) { logger.E( $"[Steering] - Control Error {e}" ); this.autoManager.ProcessAlarm( e ); } else { var msg = new DriveControlEventArgs() { EventDir = DriveControlEventArgs.eEventDir.ToFront, ControlKind = DriveControlEventArgs.eControlKind.Steering, Result = FluentResults.Results.Ok( DriveControlEventArgs.eMoveDir.LEFT ), }; this.eventAggregator.GetEvent().Publish( msg ); } } #endregion } }