using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using FluentResults; using GSG.NET.Concurrent; using GSG.NET.Extensions; 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.Drive; using VehicleControlSystem.ControlLayer.IO; using VehicleControlSystem.ControlLayer.Serial.BatteryTabos; using VehicleControlSystem.ControlLayer.Serial.DataModel; 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 /// /// Tag 위치 /// private string currentTag = "0000"; public string CurrentTag { get { return currentTag; } set { if ( SetField( ref this.currentTag, value ) ) { var info = sql.VehicleInfoDAL.GetInfo(); info.CurrentTag = value; sql.VehicleInfoDAL.Update( info ); this.OnCurrentTagChanged?.Invoke( value ); } } } /// /// Scale Value /// private double currentPosition; public double CurrentPosition { get { return currentPosition; } set { if ( SetField( ref this.currentPosition, value ) ) { } } } private double currentSpeed; public double CurrentSpeed { get { return currentSpeed; } set { SetField( ref this.currentSpeed, value ); } } private double currentTorque; public double CurrentTorque { get { return currentTorque; } set { SetField( ref this.currentTorque, value ); } } private bool isContain; public bool IsContain { get { return isContain; } set { SetField( ref this.isContain, value ); } } eClampState _clampState; public eClampState ClampState { get { return this._clampState; } set { this.SetField( ref this._clampState, value ); } } private eSteeringState steeringState; public eSteeringState SteeringState { get { return steeringState; } set { SetField( ref this.steeringState, value ); } } private int _obstacleDrive; public int ObstacleDrive { get { return this._obstacleDrive; } set { SetField( ref this._obstacleDrive, value ); } } private int _obstacleCurve; public int ObstacleCurve { get { return this._obstacleCurve; } set { SetField( ref this._obstacleCurve, value ); } } private eObstacleState obstacleState = eObstacleState.Normal; public eObstacleState ObstacleStateProperty { get { return obstacleState; } set { if ( SetField( ref this.obstacleState, value ) ) { if ( value == eObstacleState.Blocked ) this.VehicleStateProperty = eVehicleState.Blocked; } } } private eVehicleState vehicleState = eVehicleState.None; public eVehicleState VehicleStateProperty { get { return vehicleState; } set { if ( SetField( ref this.vehicleState, value ) ) { var info = sql.VehicleInfoDAL.GetInfo(); info.VehicleState = value; sql.VehicleInfoDAL.Update( info ); } } } private eMachineMode machineMode = eMachineMode.LocalMode; public eMachineMode MachineMode { get { return machineMode; } set { if ( SetField( ref this.machineMode, value ) ) { var info = sql.VehicleInfoDAL.GetInfo(); info.MachineMode = value; sql.VehicleInfoDAL.Update( info ); } } } private int obstaclePattern; public int ObstaclePattern { get { return obstaclePattern; } set { SetField( ref this.obstaclePattern, value ); } } //이동 public bool Busy { get { return this.CurrentSubCommand == null ? false : true; } set { } } public bool IsMoving { get; set; } #region Battery Property double batteryVoltage; public double BatteryVoltage { get { return this.batteryVoltage; } set { this.SetField( ref this.batteryVoltage, value ); } } double batteryCurrent; public double BatteryCurrent { get { return this.batteryCurrent; } set { this.SetField( ref this.batteryCurrent, value ); } } double batteryState; public double BatteryState { get { return this.batteryState; } set { this.SetField( ref this.batteryState, value ); } } double batteryChargeTime; public double BatteryChargeTime { get { return this.batteryChargeTime; } set { this.SetField( ref this.batteryChargeTime, value ); } } double batteryDisChargeTime; public double BatteryDisChargeTime { get { return this.batteryDisChargeTime; } set { this.SetField( ref this.batteryDisChargeTime, value ); } } double batteryStateOfCharge; public double BatteryStateOfCharge { get { return this.batteryStateOfCharge; } set { this.SetField( ref this.batteryStateOfCharge, value ); } } double batteryStateOfHealth; public double BatteryStateOfHealth { get { return this.batteryStateOfHealth; } set { this.SetField( ref this.batteryStateOfHealth, value ); } } double batteryCapacity; public double BatteryCapacity { get { return this.batteryCapacity; } set { this.SetField( ref this.batteryCapacity, value ); } } double batteryEnergy; public double BatteryEnergy { get { return this.batteryEnergy; } set { this.SetField( ref this.batteryEnergy, value ); } } double batteryTemperature; public double BatteryTemperature { get { return this.batteryTemperature; } set { this.SetField( ref this.batteryTemperature, value ); } } bool batteryIsConnect; public bool BatteryIsConnect { get { this.BatteryIsConnect = this.bMUManager.IsConnected; return this.bMUManager.IsConnected; } set { this.SetField( ref this.batteryIsConnect, value ); } } #endregion public bool IsError { get; set; } 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 OnCurrentTagChanged; public event Action OnManualMove; public event Action OnManualLoad; public event Action OnManualUnload; public event Action OnManualCharging; public event Action OnFailReport; #endregion #region List. List cylinders = new List(); List obstacleBitList = new List(); #endregion EzIO iO = null; GSIDrive drive = null; SqliteManager sql = null; Clamp clamp = null; Steering steering = null; AutoManager autoManager = null; BMUManager bMUManager = null; private Conveyor conveyor = null; ThreadCancel cancel = new ThreadCancel(); TaskCancel taskCancel = new TaskCancel(); TaskCancel taskMoveCancel = new TaskCancel(); IEventAggregator eventAggregator; public Vehicle( IIO io, SqliteManager sqliteManager, IEventAggregator ea, AutoManager auto ) { this.iO = io as EzIO; this.iO.OnChangedIO += IO_OnChangedIO; 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; /*Drive*/ this.eventAggregator.GetEvent().Unsubscribe( ReceiveDriveControlEvent ); this.eventAggregator.GetEvent().Subscribe( ReceiveDriveControlEvent ); this.eventAggregator.GetEvent().Unsubscribe( ObstacleReceiveEvent ); this.eventAggregator.GetEvent().Subscribe( ObstacleReceiveEvent ); } private void ObstacleReceiveEvent( ObstacleControlEventArgs obj ) { if ( this.autoManager.OperationModeProperty != eOperatationMode.ManualMode ) return; if ( obj.EventDir == ObstacleControlEventArgs.eEventDir.ToBack ) { switch ( obj.ControlKind ) { case ObstacleControlEventArgs.eControlKind.NONE: break; case ObstacleControlEventArgs.eControlKind.DRIVE: //this.ChgObstacleDetectPattern( new Random().Next( 0 , 40 ) ); break; case ObstacleControlEventArgs.eControlKind.CURVE: //this.ChgObstacleDetectPattern( new Random().Next( 0 , 40 ) ); break; case ObstacleControlEventArgs.eControlKind.STATE: //var value = this.GetObstacleDetectPattern(); break; case ObstacleControlEventArgs.eControlKind.INFO: { var msg = new ObstacleControlEventArgs { ControlKind = ObstacleControlEventArgs.eControlKind.INFO, Drive = this.ObstacleDrive, Curve = this.ObstacleCurve, ObstacleState = this.ObstacleStateProperty.ToString() }; this.ObstacleControlEventPublish( msg ); } break; case ObstacleControlEventArgs.eControlKind.SAVE: { this.ObstacleCurve = obj.Curve; this.ObstacleDrive = obj.Drive; var reply = new ObstacleControlEventArgs { ControlKind = ObstacleControlEventArgs.eControlKind.SAVE }; reply.Result = Results.Ok( ObstacleControlEventArgs.eControlKind.SAVE ); this.ObstacleControlEventPublish( reply ); } break; } } } private void ObstacleControlEventPublish( ObstacleControlEventArgs args ) { args.EventDir = ObstacleControlEventArgs.eEventDir.ToFront; this.eventAggregator.GetEvent().Publish( args ); } private void ReceiveDriveControlEvent( DriveControlEventArgs _args ) { if ( this.autoManager.OperationModeProperty != eOperatationMode.ManualMode ) return; var msg = _args; if ( msg.EventDir == DriveControlEventArgs.eEventDir.ToBack ) { switch ( msg.ControlKind ) { case DriveControlEventArgs.eControlKind.MOVE: this.ReqMoveToPos( _args ); 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: { var 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; case DriveControlEventArgs.eControlKind.ReqCurrentPos: //this.ReqCurrentPos(); break; case DriveControlEventArgs.eControlKind.ReqStopCurrentPos: //this.taskCancel.Cancel(); //this.taskCancel.WaitAll(); break; case DriveControlEventArgs.eControlKind.FaultReset: this.ReqFaultReset( _args ); break; case DriveControlEventArgs.eControlKind.DriveON: this.ReqDriveOn( _args ); break; case DriveControlEventArgs.eControlKind.DriveOFF: this.ReqDriveOff( _args ); break; case DriveControlEventArgs.eControlKind.JOG: this.ReqJog( _args ); break; case DriveControlEventArgs.eControlKind.VehicleState: ReqVehicleState( _args ); break; case DriveControlEventArgs.eControlKind.NONE: break; case DriveControlEventArgs.eControlKind.Conveyor: break; default: break; } } } public void ReqConveyorMove( string dir ) { if ( dir.Equals( "CW" ) ) this.OnOffConveyor( true, false ); else if ( dir.Equals( "CCW" ) ) this.OnOffConveyor( true, true ); else if ( dir.Equals( "STOP" ) ) this.OnOffConveyor( false, false ); } private void DriveControlEventPublish( DriveControlEventArgs args ) { args.EventDir = DriveControlEventArgs.eEventDir.ToFront; this.eventAggregator.GetEvent().Publish( args ); } public void Init() { this.CreateClamp(); this.CreateSteering(); this.CreateDrive(); this.CreateBMUManager(); this.CreateConveyor(); ThreadStart(); //TimerUtils.Once(5000, () => { this.CurrentPosition = 1000; }); var v = sql.VehicleInfoDAL.GetInfo(); v.CurrentTag = "0000"; v.VehicleState = eVehicleState.None; v.MachineMode = eMachineMode.LocalMode; sql.VehicleInfoDAL.Update( v ); } public int InitializationVehicle() { #if SIMULATION this.VehicleStateProperty = eVehicleState.Idle; return 0; #else int result = 0; if ( this.IsDetectedCenter() ) //자제가 있으면 Lock result = this.clamp.Lock_Sync(); else result = this.clamp.Unlock_Sync(); if ( this.drive.IsErrorOn ) return 22; this.VehicleStateProperty = eVehicleState.Idle; return result; #endif } public void Dispose() { this.cancel.Cancel(); this.cancel.StopWaitAll(); this.bMUManager.Disconnect(); this.drive.Dispose(); } #region Request Method private void ReqVehicleState( DriveControlEventArgs args ) { VehicleInfo state = new VehicleInfo(); state.CurrentPosition = this.CurrentPosition; state.CurrentSpeed = this.CurrentSpeed; state.CurrentTag = this.CurrentTag; state.CurrentTorque = this.CurrentTorque; state.Voltage = this.BatteryVoltage; state.Current = this.BatteryCurrent; state.BatteryState = this.BatteryState; state.ChargeTime = this.BatteryChargeTime; state.DisChargeTime = this.BatteryDisChargeTime; state.SOC = this.BatteryStateOfCharge; state.SOH = this.BatteryStateOfHealth; state.Capacity = this.BatteryCapacity; state.Energy = this.BatteryEnergy; state.Temperature = this.BatteryTemperature; state.BatteryIsConnect = this.BatteryIsConnect; var msg = new DriveControlEventArgs(); msg.ControlKind = args.ControlKind; msg.Args = state; DriveControlEventPublish( msg ); } private void ReqMoveToPos( DriveControlEventArgs args ) { //var result = drive.Move( args.PositionTag ); //this.MoveTo( "1111" ); var reply = new DriveControlEventArgs(); int targetTag = args.TargetRouteID; var route = sql.RouteDal.GetK( targetTag ); if ( route == null ) { reply.Result = Results.Fail( "Not Found Route" ); this.DriveControlEventPublish( reply ); return; } this.MoveTo( route.Name ); reply.Result = Results.Ok( "Position Move" ); this.DriveControlEventPublish( reply ); } void ReqFaultReset( DriveControlEventArgs _args ) { var drive = 0; //var result = drive.ResetAmpFault(); var msg = new DriveControlEventArgs { ControlKind = DriveControlEventArgs.eControlKind.FaultReset }; msg.Result = Results.Ok( "Drive On" ); this.DriveControlEventPublish( msg ); } void ReqJog( DriveControlEventArgs _args ) { var drive = string.Empty; if ( _args.JogDir == DriveControlEventArgs.eJogMoveDir.Positive ) drive = "POSITIVE"; else drive = "NEGATIVE"; } void ReqCurrentPos() { var task = Task.Factory.StartNew( () => { while ( !this.taskCancel.Canceled ) { LockUtils.Wait( 500 ); var msg = new DriveControlEventArgs { EventDir = DriveControlEventArgs.eEventDir.ToFront, ControlKind = DriveControlEventArgs.eControlKind.ReqCurrentPos, CurrentPosition = new Random().Next( 0, 1000 ), }; this.DriveControlEventPublish( msg ); } } ); this.taskCancel.Add( task ); } void ReqDriveOn( DriveControlEventArgs _args ) { var drive = "Drive Name"; //drive.On(); var msg = new DriveControlEventArgs { ControlKind = DriveControlEventArgs.eControlKind.DriveON }; msg.Result = Results.Ok( "Drive On" ); this.DriveControlEventPublish( msg ); } void ReqDriveOff( DriveControlEventArgs _args ) { var drive = "Drive Name"; //drive.Off(); var msg = new DriveControlEventArgs { ControlKind = DriveControlEventArgs.eControlKind.DriveOFF }; msg.Result = Results.Ok( "Drive On" ); this.DriveControlEventPublish( msg ); } #endregion #region Thread void ThreadStart() { this.cancel.AddGo( new Action( this._ThSubCmdWorker ) ); this.cancel.AddGo( new Action( this._ThVehicleStateCheck ) ); } //장애물 감지 Thread //장애물 감지 패턴 변경도 여기 하자. private void _ThVehicleStateCheck() { while ( !this.cancel.Canceled ) { try { if ( this.autoManager.OperationModeProperty == eOperatationMode.AutoMode ) this.CheckObstacle(); this.CheckIOState(); } catch ( ThreadInterruptedException threadInterruptedException ) { } catch ( Exception exception ) { logger.E( exception ); } finally { LockUtils.Wait( 5 ); } } logger.D( "Vehicle - _ThObstacleChecker Dispose" ); } private void CheckIOState() { //이미 알람이면 체크 안함. if ( this.VehicleStateProperty == eVehicleState.Abnormal ) return; if ( !this.iO.IsConnectError ) return; if ( this.iO.IsOn( "IN_EMS_SW" ) ) this.OccurVehicleAlarm( 28 ); if ( !this.iO.IsOn( "IN_CP_ON_SAFETY" ) ) this.OccurVehicleAlarm( 31 ); if ( !this.iO.IsOn( "IN_CP_ON_24V" ) ) this.OccurVehicleAlarm( 30 ); if ( !this.iO.IsOn( "IN_MC_ON" ) ) this.OccurVehicleAlarm( 29 ); } /// /// 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 eSubCommandType.Move: this.CurrentSubCommand = subCmd; this.Move( subCmd ); break; case eSubCommandType.Load: this.CurrentSubCommand = subCmd; this.LoadCarrier( subCmd ); break; case eSubCommandType.Unload: this.CurrentSubCommand = subCmd; this.UnloadCarrier( subCmd ); break; case eSubCommandType.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 public void EStop() { OnOffConveyor( false ); //Clamp EStop this.clamp.ClampEStop(); this.drive.EStop(); this.OccurVehicleAlarm( 23 ); } #region For Moving void Move( SubCmd sub ) { if ( this.MoveTo( sub.TargetID ) ) { } sql.SubCmdDAL.Delete( sub ); } bool MoveTo( string pointID ) { //this.BuzzerOnOff(true, eBuzzerKind.StartWarn); ////TimerUtils.Once(3000, BuzzerOnOff, false, eBuzzerKind.StartWarn ); //Thread.Sleep(3000); //this.BuzzerOnOff(false); if ( this.VehicleStateProperty == eVehicleState.Idle ) { this.OnMoveReady?.Invoke(); var moveReadyBuzzerTime = sql.ConfigDal.GetValueToInt( ConstString.BuzzerStartReadyTime ); Thread.Sleep( moveReadyBuzzerTime ); this.VehicleStateProperty = eVehicleState.Move; } this.OnMoving?.Invoke(); this.IsMoving = true; //이전에 있던 작업들 종료 및 삭제 this.taskMoveCancel.Cancel(); this.taskMoveCancel.WaitAll(); this.taskMoveCancel.Add( CheckCrossPoint() ); //this.BuzzerOnOff(true, eBuzzerKind.Moving); this.drive.MoveToPoint( pointID, 100 ); bool result = Wait4MoveDone(); //this.BuzzerOnOff(false); this.taskMoveCancel.Cancel(); this.taskMoveCancel.WaitAll(); if ( drive.IsStop ) { this.IsMoving = false; this.OnMoveFinish?.Invoke(); this.VehicleStateProperty = eVehicleState.Idle; } //PhysicalCheckupLogger.Instance.UploadPhysicalCheckupLog(); return result; } bool Wait4MoveDone() { int waitTime = 9000; //설정 할 수있게. long st = SwUtils.CurrentTimeMillis; //Todo: 이동시 확인 사항들. while ( true ) { Thread.Sleep( 5 ); if ( SwUtils.Gt( st, waitTime ) ) { //Todo: 이동시간 초과 시 동작들. break; } //Todo: 이동중 명령이 삭제 되면 처리 할일들. //이동중 메인 명력이 없어진다면 정지 후 if ( !sql.CommandDAL.HasK( this.CurrentSubCommand.CmdID ) ) { logger.D( "[Wait Move Done] - 메인 명령 사라짐" ); var cmd = sql.CommandDAL.GetCmd(); if ( cmd == null ) { logger.D( "[Wait Move Done] - Main Command not Exist Motion Stop" ); drive.Stop(); return true; } else { logger.D( "[Wait Move Done] - Main Command not Exist Motion command 없음" ); return true; } } PhysicalCheckupLogger.Instance.DriveStateLog( this.drive.CurrentSpeed.ToString(), this.drive.CurrentTorque.ToString() ); //20.04.04 Kang Drive 측으로 상태만 알려주면 알아서 처리함. //if ( this.ObstacleStateProperty != eObstacleState.Normal ) //{ // if ( this.ObstacleStateProperty == eObstacleState.Blocked ) // this.VehicleStateProperty = eVehicleState.Blocked; // if ( this.ObstacleStateProperty == eObstacleState.Abnormal ) // { // this.VehicleStateProperty = eVehicleState.Abnormal; // this.OccurVehicleAlarm( 24 ); // return false; // } //} } return true; } Task CheckCrossPoint() { var task = Task.Run( () => { long sTime = SwUtils.CurrentTimeMillis; while ( !this.taskMoveCancel.Canceled ) { Thread.Sleep( 10 ); //ToDo: approach Cross Point Check //ToDo: Obstacle Laser Sensor Pattern Change Method 구현 필요. } } ); return task; } #endregion public bool LoadCarrier( SubCmd sub ) { this.VehicleStateProperty = eVehicleState.Load; var route = sql.RouteDal.GetRoute( sub.TargetID ); if ( !CorrectPosition( route, this.CurrentPosition ) ) { this.OccurVehicleAlarm( 20 ); return false; //Alarm } int result = this.clamp.Unlock_Sync(); if ( result != 0 ) { this.OccurVehicleAlarm( result ); return false; } this.iO.OutputOn( "OUT_PIO_SENSOR_ONOFF" ); result = this.PIOAndLoad( sub.TargetID ); if ( result != 0 ) { this.iO.OutputOff( "OUT_PIO_SENSOR_ONOFF" ); this.OccurVehicleAlarm( result ); return false; } this.iO.OutputOff( "OUT_PIO_SENSOR_ONOFF" ); result = this.clamp.Lock_Sync(); if ( result != 0 ) { this.OccurVehicleAlarm( result ); return false; } //Load, Unload 가 끝나면 메인 Command 를 완료 했다고 판단. sql.CommandDAL.UpdateState( sub.CmdID, eCommandState.Complete ); sql.SubCmdDAL.Delete( sub ); this.VehicleStateProperty = eVehicleState.Idle; return true; } public bool UnloadCarrier( SubCmd sub ) { this.VehicleStateProperty = eVehicleState.Unload; var route = sql.RouteDal.GetRoute( sub.TargetID ); if ( !CorrectPosition( route, this.CurrentPosition ) ) { this.OccurVehicleAlarm( 21 ); return false; //Alarm } int result = this.clamp.Unlock_Sync(); if ( result != 0 ) { this.OccurVehicleAlarm( result ); return false; } this.iO.OutputOn( "OUT_PIO_SENSOR_ONOFF" ); result = this.PIOAndUnload( sub.TargetID ); if ( result != 0 ) { this.iO.OutputOff( "OUT_PIO_SENSOR_ONOFF" ); this.OccurVehicleAlarm( result ); return false; } this.iO.OutputOff( "OUT_PIO_SENSOR_ONOFF" ); sql.CommandDAL.UpdateState( sub.CmdID, eCommandState.Complete ); sql.SubCmdDAL.Delete( sub ); this.VehicleStateProperty = eVehicleState.Idle; return true; } public void BatteryCharge( SubCmd subCmd ) { this.VehicleStateProperty = eVehicleState.Charge; this.iO.OutputOn( "OUT_PIO_SENSOR_ONOFF" ); this.PIOBatteryCharge( subCmd ); this.iO.OutputOff( "OUT_PIO_SENSOR_ONOFF" ); sql.CommandDAL.UpdateState( subCmd.CmdID, eCommandState.Complete ); sql.SubCmdDAL.Delete( subCmd ); this.VehicleStateProperty = eVehicleState.Idle; } /// /// Battery Charge /// 충전 시 PIO 를 해야 함. /// /// /// int PIOBatteryCharge( SubCmd sub ) { var route = sql.RouteDal.GetRoute( sub.TargetID ); if ( !CorrectPosition( route, this.CurrentPosition ) ) { this.OccurVehicleAlarm( 21 ); return 0; //Alarm } var pioTimeout = sql.ConfigDal.GetValueToInt( ConstString.PIOTimeOut ); PIOClear(); loggerPIO.I( $"Start Charge PIO - [{sub.TargetID}]" ); if ( !this.iO.IsOn( "IN_PIO_READY" ) ) { loggerPIO.E( "[Port] - 1 Ready not On" ); this.iO.OutputOff( "OUT_PIO_SENSOR_ONOFF" ); this.OccurVehicleAlarm( 25 ); 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.OccurVehicleAlarm( 26 ); return 0; } this.iO.WriteOutputIO( "OUT_PIO_SENDING_RUN", true ); loggerPIO.I( "[Vehicle] - 2 Send Run On" ); var sTime = SwUtils.CurrentTimeMillis; while ( true ) { Thread.Sleep( 5 ); if ( !this.iO.IsOn( "IN_PIO_READY" ) || this.iO.IsOn( "IN_PIO_RECEIVE_RUN" ) ) break; if ( !sql.CommandDAL.HasK( this.CurrentSubCommand.CmdID ) ) { PIOClear(); logger.D( "[Wait Charging] - 메인 명령 사라짐" ); break; } } if ( !this.iO.WaitChangeInputIO( true, pioTimeout, "IN_PIO_RECEIVE_COMPLITE" ) ) { PIOClear(); loggerPIO.E( "[Port] - 3 Receive Complete Timeout" ); this.OccurVehicleAlarm( 26 ); return 0; } PIOClear(); this.iO.WriteOutputIO( "OUT_PIO_SEND_COMPLITE", true ); Thread.Sleep( 1000 ); this.iO.WriteOutputIO( "OUT_PIO_SEND_COMPLITE", false ); return 0; } #endregion #region Check Method bool CheckObstacle() { //if ( this.iO.IsOn( "IN_OBSTRUCTION_DETECT_SAFETY" ) || this.iO.IsOn( "IN_OBSTRUCTION_DETECT_ERROR" ) ) //{ // this.ObstacleStateProperty = eObstacleState.Abnormal; // this.motion.SetObstacleState( this.ObstacleStateProperty ); // return true; //} //if ( this.iO.IsOn( "IN_OBSTRUCTION_DETECT_STOP" ) ) //{ // this.ObstacleStateProperty = eObstacleState.Blocked; // this.motion.SetObstacleState( this.ObstacleStateProperty ); // return true; //} //if ( this.iO.IsOn( "IN_OBSTRUCTION_DETECT_SLOW" ) ) //{ // this.ObstacleStateProperty = eObstacleState.Decelerate; // this.motion.SetObstacleState( this.ObstacleStateProperty ); // return true; //} this.ObstacleStateProperty = eObstacleState.Normal; this.drive.SetObstacleState( this.ObstacleStateProperty ); return false; } #endregion #region Mechanical Method #region Conveyor /// /// (Run = true, CW = true CCW = false) /// /// /// bit On 시 Unload 방향 진행. /// int OnOffConveyor( bool isOn, bool isLoad = false ) { if ( IsInverterError() ) return 16; if ( isLoad ) 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 IsCvRun() => this.iO.IsOn( "OUT_CV_RUN" ); bool IsCvCWCCW() => this.iO.IsOn( "OUT_CV_CWCCW" ); /// /// 입구 감지 로딩시 감속 사용 /// /// 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" ); //Normal Close 로 생각 됨. bool IsLifterPositinCheck() => this.iO.IsOn( "IN_LIFTER_POSITION_DETECT" ); bool IsLifterDuplication() => this.iO.IsOn( "IN_LIFTER_DUPLICATION_DETECT" ); bool IsPIOInterLockOn() => this.iO.IsOn( "IN_PIO_INTERLOCK" ); public int ConveyorLoad() => this.conveyor.ConveyorLoad(); public int ConveyorUnload() => this.conveyor.ConveyorUnload(); 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 ); Thread.Sleep( 1000 ); this.OnLoadComplete?.Invoke(); #else var pioTimeout = sql.ConfigDal.GetValueToInt( ConstString.PIOTimeOut ); if ( this.IsInverterError() ) return 16; if ( !this.IsLifterPositinCheck() ) return 14; //Todo: Sensor Setting 이 후 주석 풀기. //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_RECEIVABLE", true ); loggerPIO.I( "[Vehicle] - 4 Receivable" ); 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, 1000 ); //1Sec 이후 On 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; } loggerPIO.I( "[Port] - 5 Sending Run On" ); bool isStartDetected = false; var sTime = SwUtils.CurrentTimeMillis; while ( true ) { LockUtils.Wait( 10 ); 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 ); loggerPIO.I( "[Vehicle] Conveyor Stop" ); this.iO.WriteOutputIO( "OUT_PIO_RECIVE_COMPLITE", true ); loggerPIO.I( "[Vehicle] Receive Complete On" ); if ( !this.iO.WaitChangeInputIO( true, pioTimeout, "IN_PIO_SEND_COMPLITE" ) ) { this.iO.WriteOutputIO( "OUT_PIO_RECIVE_COMPLITE", false ); loggerPIO.E( "[Port] IN_PIO_SEND_COMPLITE On Time Out" ); } loggerPIO.I( "[Port] Send Complete On" ); this.iO.WriteOutputIO( "OUT_PIO_RECIVE_COMPLITE", false, 1000 ); this.OnLoadComplete?.Invoke(); loggerPIO.I( $"End Load PIO - [{targetName}]" ); #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(); Thread.Sleep( 1000 ); 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; } loggerPIO.E( "[Port] - 2 Receive CV Run On" ); 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 ( !IsDetectedLoadStart() && !IsDetectedCenter() ) { if ( this.iO.IsOn( "IN_PIO_RECEIVE_COMPLITE" ) ) { loggerPIO.I( "[Port] - 3 Receive Complete On" ); break; } } } if ( !IsDetectedCenter() ) this.OnCarrierDetected?.Invoke( false ); this.OnOffConveyor( false ); //Stop this.OnConveyorStop?.Invoke( false ); PIOClear(); this.iO.WriteOutputIO( "OUT_PIO_SEND_COMPLITE", true ); Thread.Sleep( 1000 ); this.iO.WriteOutputIO( "OUT_PIO_SEND_COMPLITE", false ); loggerPIO.I( "[Vehicle] - 3 Send Complete OnOff" ); this.OnUnloadComplete?.Invoke(); #endif loggerPIO.I( $"End Unload PIO - [{targetName}]" ); 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; this.steering.PropertyChanged += Steering_PropertyChanged; } void CreateClamp() { this.clamp = new Clamp( this.sql, this.eventAggregator ); this.clamp.Init(); this.clamp.PropertyChanged += Clamp_PropertyChanged; } void CreateDrive() { this.drive = new GSIDrive( this.sql ); this.drive.PropertyChanged += Motion_PropertyChanged; this.drive.Init(); } void CreateConveyor() { this.conveyor = new Conveyor( this.iO ); } void CreateBMUManager() { this.bMUManager = new BMUManager(); this.bMUManager.BMUConfig = new Serial.BatteryTabos.Config() { ID = "0" }; this.bMUManager.OnConnect += BMUManager_OnConnect; this.bMUManager.OnDisconnect += BMUManager_OnDisconnect; this.bMUManager.OnChangedReceivedData += BMUManager_OnChangedReceivedData; this.bMUManager.OnFirstColtd += BMUManager_OnFirstColtd; var setV = sql.ConfigDal.GetValueToInt( ConstString.BatteryCanType ); var canType = CastTo.From( setV ); this.bMUManager.Connect( canType ); } private void BMUManager_OnDisconnect( string obj, bool state ) { this.BatteryIsConnect = state; this.OccurVehicleAlarm( 32 ); } private void BMUManager_OnConnect( string obj, bool state ) { this.BatteryIsConnect = state; } private void BMUManager_OnFirstColtd( List obj ) { } private void BMUManager_OnChangedReceivedData( Serial.DataModel.ReceivedData obj ) { var kind = CastTo.From( obj.DataKind ); switch ( kind ) { case eDataKind.Voltage: this.BatteryVoltage = (double)obj.Value * obj.Scale; break; case eDataKind.Current: this.BatteryCurrent = (double)obj.Value * obj.Scale; break; case eDataKind.BatteryState: if ( obj.Value == null ) return; this.BatteryState = (double)obj.Value; break; case eDataKind.ChargeCompleteTime: if ( obj.Value == null || obj.Value <= 0 ) return; this.BatteryChargeTime = (double)obj.Value / obj.Scale; break; case eDataKind.DisChargeCompleteTime: if ( obj.Value == null || obj.Value <= 0 ) return; this.BatteryDisChargeTime = (double)obj.Value / obj.Scale; break; case eDataKind.SOC: this.BatteryStateOfCharge = (double)obj.Value * obj.Scale; break; case eDataKind.SOH: this.BatteryStateOfHealth = (double)obj.Value * obj.Scale; break; case eDataKind.ResidualCapacity: this.BatteryCapacity = (double)obj.Value * obj.Scale; break; case eDataKind.ResidualEnergy: this.BatteryEnergy = (double)obj.Value * obj.Scale; break; case eDataKind.Temperature: this.BatteryTemperature = (double)obj.Value * obj.Scale; break; default: break; } } #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 /// public 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++; } ); ObstaclePattern = no; return true; } public int GetObstacleDetectPattern() { int bitIndex = 0; BitArray bitArray = new BitArray( this.obstacleBitList.Count ); this.obstacleBitList.ForEach( b => { if ( this.iO.IsOn( b, false ) ) bitArray.Set( bitIndex, false ); else bitArray.Set( bitIndex, true ); bitIndex++; } ); return BitUtils.ChgInt32( bitArray ); } void OccurVehicleAlarm( int alarmID ) { this.MachineMode = eMachineMode.LocalMode; this.VehicleStateProperty = eVehicleState.Abnormal; this.autoManager.ProcessAlarm( alarmID ); } public void SetObstaclePattern( ObstacleControlEventArgs.eControlKind state, int value ) { if ( state == ObstacleControlEventArgs.eControlKind.DRIVE ) { this.ObstacleDrive = value; ChgObstacleDetectPattern( this.ObstacleDrive ); } else if ( state == ObstacleControlEventArgs.eControlKind.CURVE ) { this.ObstacleCurve = value; ChgObstacleDetectPattern( this.ObstacleCurve ); } else return; } #endregion #region Event Subscribe private void Motion_PropertyChanged( object sender, System.ComponentModel.PropertyChangedEventArgs e ) { var property = sender.GetType().GetProperty( e.PropertyName ); var newValue = property.GetValue( sender, null ); if ( e.PropertyName.Equals( "CurrentPos" ) ) { var v = CastTo.From( newValue ); this.CurrentPosition = v; } if ( e.PropertyName.Equals( "CurrentTag" ) ) { var v = CastTo.From( newValue ); this.CurrentTag = v; } if ( e.PropertyName.Equals( "CurrentSpeed" ) ) { var v = CastTo.From( newValue ); this.CurrentSpeed = v; } if ( e.PropertyName.Equals( "CurrentTorque" ) ) { var v = CastTo.From( newValue ); this.CurrentTorque = v; } if ( e.PropertyName.Equals( "ReqSteeringState" ) ) { var v = CastTo.From( newValue ); switch ( v ) { case eSteeringState.None: break; case eSteeringState.Left: this.steering.ControlSteering(true); break; case eSteeringState.Right: this.steering.ControlSteering(); break; default: break; } } } private void Steering_PropertyChanged( object sender, System.ComponentModel.PropertyChangedEventArgs e ) { var property = sender.GetType().GetProperty( e.PropertyName ); var newValue = property.GetValue( sender, null ); //Todo: 나중에 Test 하자 //var ownPropperty = this.GetType().GetProperty(e.PropertyName); if ( e.PropertyName.Equals( "SteeringState" ) ) { var v = CastTo.From( newValue ); this.SteeringState = v; } } private void Clamp_PropertyChanged( object sender, System.ComponentModel.PropertyChangedEventArgs e ) { var property = sender.GetType().GetProperty( e.PropertyName ); var newValue = property.GetValue( sender, null ); if ( e.PropertyName.Equals( "ClampState" ) ) { var v = CastTo.From( newValue ); this.ClampState = v; } } private void IO_OnChangedIO( BitBlock bit ) { if ( bit.Tag.Equals( "IN_CV_DETECT_01" ) ) { this.IsContain = bit.IsBitOn; } } private void Steering_OnSteeringError( object sender, int e ) { if ( e != 0 ) { logger.E( $"[Steering] - Control Error {e}" ); this.OccurVehicleAlarm( 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 } }