// Copyright (c) 2020 LG Electronics Inc. // SPDX-License-Identifier: LicenseRef-LGE-Proprietary using Aurora; using Aurora.Api; using Aurora.Api.Devices; using Aurora.View; using System; using System.Collections.Generic; using System.Reactive.Linq; using System.Windows; using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; namespace AuroraMotor.Views { public class UCAuroraMotorViewModel : NotifyPropertyChangedBase { static readonly Logger Log = new Logger("UCAuroraMotorViewModel"); static readonly SolidColorBrush ColorMoving = new SolidColorBrush(Color.FromRgb(0, 200, 0)); static readonly SolidColorBrush ColorStopped = Application.Current.Resources["MahApps.Brushes.Text"] as SolidColorBrush ?? new SolidColorBrush(Colors.GhostWhite); static readonly SolidColorBrush ColorAlarm = new SolidColorBrush(Colors.DarkRed); static readonly TimeSpan TIMESPAN_NORMAL = TimeSpan.FromSeconds(1); static readonly TimeSpan TIMESPAN_FREQUENT = TimeSpan.FromMilliseconds(300); readonly IEventObservable mEventObservable; readonly DispatcherTimer mMonitorTimer; readonly DispatcherTimer mCurrentPosUpdateTimer; IDisposable mDevActiveObserveDisposer; AuroraMotorBase mActivatedMotor; public AuroraMotorBase mSelectedMotor; public AuroraMotorBase SelectedMotor { get => mSelectedMotor; set { if (mSelectedMotor == value) return; mSelectedMotor = value; OnMotorActiveChanged(); NotifyPropertyChanged(null); OnMonitorTime(); } } public Range2 PosLimit => new Range2( mActivatedMotor?.GetPosLimit() ?? (0.0, 0.0) ); public Range VelLimit => new Range( mActivatedMotor?.GetVelLimit() ?? (0, 0) ); public bool IsServoOn { get => mActivatedMotor?.IsServoOn() ?? false; set { mActivatedMotor?.ServoOn(value); NotifyPropertyChanged(); } } public IList Alarms { get; private set; } public double ActualPos => mActivatedMotor?.GetActualPos() ?? System.Double.NaN; private Brush mCurrentPosBrush; public Brush CurrentPosBrush { get => mCurrentPosBrush; private set => SetProperty(ref mCurrentPosBrush, value); } public string HomeStatus => Application.Current.FindStrToken( mActivatedMotor == null ? "NA" : mActivatedMotor.IsHomeCompleted() ? "Completed" : "NotCompleted" ); public double TargetJogVel { get; set; } public double TargetJogIncrement { get; set; } public bool IsAbsTarget { get; set; } public bool IsRelTarget { get; set; } public double TargetPos { get; set; } public double TargetVel { get; set; } public ICommand CMD_ViewLoaded { get; } public ICommand CMD_ViewUnloaded { get; } public ICommand CMD_ClearAlarm { get; } public ICommand CMD_Home { get; } public ICommand CMD_Move { get; } public ICommand CMD_JogPositive { get; } public ICommand CMD_JogNegative { get; } public ICommand CMD_JogGo { get; } public ICommand CMD_Stop { get; } #region 생성자 public UCAuroraMotorViewModel(IEventObservable eventObservable) { mEventObservable = eventObservable; mActivatedMotor = null; IsAbsTarget = true; CMD_ViewLoaded = new DelegateCommand(OnViewLoaded); CMD_ViewUnloaded = new DelegateCommand(OnViewUnloaded); CMD_ClearAlarm = new DelegateCommand(HandleClearAlarm); CMD_Home = new DelegateCommand(HandleHome); CMD_Move = new DelegateCommand(HandleMove); CMD_JogPositive = new DelegateCommand((_) => HandleJog(+1)); CMD_JogNegative = new DelegateCommand((_) => HandleJog(-1)); CMD_JogGo = new DelegateCommand(HandleJogGo); CMD_Stop = new DelegateCommand(HandleStop); mMonitorTimer = CreateTimer( TIMESPAN_NORMAL, OnMonitorTime ); mCurrentPosUpdateTimer = CreateTimer( TIMESPAN_NORMAL, OnActualPosUpdateTime ); } #endregion static DispatcherTimer CreateTimer( TimeSpan interval, Action onTime) { var timer = new DispatcherTimer(); timer.Tick += (s, e) => onTime(); timer.Interval = interval; return timer; } void OnViewLoaded(object obj) { AuroraMotorBase.MotorMoved += OnMotorMoved; mDevActiveObserveDisposer = Observable.Merge( mEventObservable.AsObservable(AuroraSysEvent.SYS_DEVICE_ACTIVATED), mEventObservable.AsObservable(AuroraSysEvent.SYS_DEVICE_DEACTIVATED) ).Where( x => x.Param as string == mSelectedMotor?.DeviceId ).ObserveOn( Application.Current.Dispatcher ).Subscribe( x => OnMotorActiveChanged() ); OnMotorActiveChanged(); mMonitorTimer.Start(); mCurrentPosUpdateTimer.Interval = TIMESPAN_NORMAL; mCurrentPosUpdateTimer.Start(); } void OnViewUnloaded(object obj) { AuroraMotorBase.MotorMoved -= OnMotorMoved; mDevActiveObserveDisposer?.Dispose(); mMonitorTimer.Stop(); mCurrentPosUpdateTimer.Stop(); } void OnMonitorTime() { if (mActivatedMotor == null || !mActivatedMotor.IsActivated) return; NotifyPropertyChanged("IsServoOn"); NotifyPropertyChanged("HomeStatus"); SetMoving( mActivatedMotor?.IsMoving() ?? false ); if (mActivatedMotor?.IsAlarm() ?? false) { Alarms = mActivatedMotor.GetAlarm(); CurrentPosBrush = ColorAlarm; } else Alarms = null; NotifyPropertyChanged("Alarms"); } void OnActualPosUpdateTime() { NotifyPropertyChanged("ActualPos"); if (!mActivatedMotor?.IsMoving() ?? false) mCurrentPosUpdateTimer.Interval = TIMESPAN_NORMAL; } void OnMotorMoved(object sender, EventArgs e) { if ( sender is AuroraMotorBase motor && mActivatedMotor != null && motor.DeviceId == mActivatedMotor.DeviceId) mCurrentPosUpdateTimer.Interval = TIMESPAN_FREQUENT; } void OnMotorActiveChanged() { mActivatedMotor = mSelectedMotor?.IsActivated ?? false ? mSelectedMotor : null; NotifyPropertyChanged(null); } void SetMoving(bool moving) => CurrentPosBrush = moving ? ColorMoving : ColorStopped; void HandleClearAlarm(object obj) => mActivatedMotor?.ClearAlarm(); void HandleHome(object obj) { int err = mActivatedMotor?.Home() ?? -100; if (err == 0) SetMoving(true); else Log.W("ERROR: fail to home"); } void HandleMove(object obj) { int err = mActivatedMotor?.SetTargetSPD( TargetVel ) ?? -100; if (err != 0) { Log.W($"ERROR: fail to move velocity: {TargetVel}"); return; } var targetType = IsAbsTarget ? TargetPosType.ABS : TargetPosType.REL; err = mActivatedMotor?.SetTargetPos( TargetPos, targetType ) ?? -200; if (err != 0) { Log.W($"ERROR: fail to move target: ({targetType} {TargetPos:f3})"); return; } err = mActivatedMotor?.Move() ?? - 300; if (err == 0) SetMoving(true); else Log.W("ERROR: fail to move"); } void HandleJog(int sign) { int? err = mActivatedMotor?.Jog( sign, TargetJogVel ); if (err == 0) SetMoving(true); else Log.W("ERROR: fail to jog increment"); } void HandleJogGo(object obj) { int? err = mActivatedMotor?.JogIncrement( TargetJogIncrement, TargetJogVel ); if (err == 0) SetMoving(true); else Log.W("ERROR: fail to jog"); } void HandleStop(object obj) => mActivatedMotor?.Stop(); } }