using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using Tango.BL; using Tango.BL.Entities; using Tango.Core.Commands; using Tango.Core.DI; using Tango.Integration.ExternalBridge; using Tango.Logging; using Tango.MachineStudio.Common; using Tango.MachineStudio.Common.Authentication; using Tango.MachineStudio.Common.Diagnostics; using Tango.MachineStudio.Common.EventLogging; using Tango.MachineStudio.Common.Messages; using Tango.MachineStudio.Common.Modules; using Tango.MachineStudio.Common.Navigation; using Tango.MachineStudio.Common.Notifications; using Tango.MachineStudio.Common.Speech; using Tango.MachineStudio.Common.StudioApplication; using Tango.MachineStudio.Common.Update; using Tango.MachineStudio.UI.StudioApplication; using Tango.MachineStudio.UI.SupervisingController; using Tango.MachineStudio.UI.TFS; using Tango.MachineStudio.UI.Views; using Tango.MachineStudio.UI.Windows; using Tango.PMR.Stubs; using Tango.Settings; using Tango.SharedUI; using Tango.SharedUI.Controls; using Tango.SharedUI.Helpers; using Tango.Transport; using Tango.Transport.Adapters; namespace Tango.MachineStudio.UI.ViewModels { /// /// Represents the Machine Studio main view, view model. /// /// public class MainViewVM : StudioViewModel { private IStudioModule _currentModule; private INavigationManager _navigation; private bool _isDisconnecting; private Thread _updateCheckThread; private IEventLogger _eventLogger; private MachineStudioSettings _settings; /// /// Gets or sets the current loaded module. /// public IStudioModule CurrentModule { get { return _currentModule; } set { _currentModule = value; RaisePropertyChangedAuto(); } } private bool _isApplicationReady; public bool IsApplicationReady { get { return _isApplicationReady; } set { _isApplicationReady = value; RaisePropertyChangedAuto(); } } private bool _isModuleLoaded; /// /// Gets or sets a value indicating whether any module is loaded at the moment. /// public bool IsModuleLoaded { get { return _isModuleLoaded; } set { _isModuleLoaded = value; RaisePropertyChangedAuto(); } } private bool _isMenuOpened; /// /// Gets or sets a value indicating whether the side menu is opened. /// public bool IsMenuOpened { get { return _isMenuOpened; } set { _isMenuOpened = value; RaisePropertyChangedAuto(); } } private bool _isMachineErrorsOpened; /// /// Gets or sets a value indicating whether to display the machine errors. /// public bool IsMachineErrorsOpened { get { return _isMachineErrorsOpened; } set { _isMachineErrorsOpened = value; RaisePropertyChangedAuto(); } } /// /// Gets or sets the start module command. /// public RelayCommand StartModuleCommand { get; set; } /// /// Gets or sets the open module in window command. /// public RelayCommand OpenModuleInWindowCommand { get; set; } /// /// Gets or sets the home command. /// public RelayCommand HomeCommand { get; set; } /// /// Gets or sets the connect command. /// public RelayCommand ConnectCommand { get; set; } /// /// Gets or sets the disconnect command. /// public RelayCommand DisconnectCommand { get; set; } /// /// Gets or sets the sign-out command. /// public RelayCommand SignoutCommand { get; set; } /// /// Gets or sets the exit command. /// public RelayCommand ExitCommand { get; set; } /// /// Gets or sets the update center command. /// public RelayCommand UpdateCenterCommand { get; set; } /// /// Gets or sets the toggle speech command. /// public RelayCommand ToggleSpeechCommand { get; set; } /// /// Gets or sets the display HTML command. /// public RelayCommand ResolveMachineEventCommand { get; set; } /// /// Gets or sets the report issue command. /// public RelayCommand ReportIssueCommand { get; set; } /// /// Gets or sets the open resolved bugs. /// public RelayCommand OpenResolvedBugsCommand { get; set; } /// /// Gets or sets the open developer console command. /// public RelayCommand OpenDeveloperConsoleCommand { get; set; } /// /// Gets or sets the about command. /// public RelayCommand AboutCommand { get; set; } private IAuthenticationProvider _authenticationProvider; /// /// Gets or sets the authentication provider. /// public IAuthenticationProvider AuthenticationProvider { get { return _authenticationProvider; } set { _authenticationProvider = value; RaisePropertyChangedAuto(); } } private IStudioModuleLoader _studioModuleLoader; /// /// Gets or sets the studio module loader. /// public IStudioModuleLoader StudioModuleLoader { get { return _studioModuleLoader; } set { _studioModuleLoader = value; RaisePropertyChangedAuto(); } } private INotificationProvider _notificationProvider; /// /// Gets or sets the notification provider. /// public INotificationProvider NotificationProvider { get { return _notificationProvider; } set { _notificationProvider = value; RaisePropertyChangedAuto(); } } private IStudioApplicationManager _applicationManager; /// /// Gets or sets the application manager. /// public IStudioApplicationManager ApplicationManager { get { return _applicationManager; } set { _applicationManager = value; RaisePropertyChangedAuto(); } } /// /// Gets or sets the TFS client. /// public TeamFoundationServiceExtendedClient TFSClient { get; set; } /// /// Gets or sets the speech provider. /// public ISpeechProvider SpeechProvider { get; set; } private bool _isUpdateAvailable; /// /// Gets or sets a value indicating whether a new version update is available. /// public bool IsUpdateAvailable { get { return _isUpdateAvailable; } set { _isUpdateAvailable = value; RaisePropertyChangedAuto(); } } private String _latestVersion; /// /// Gets or sets the latest version. /// public String LatestVersion { get { return _latestVersion; } set { _latestVersion = value; RaisePropertyChangedAuto(); } } private bool _disableCheckForUpdates; /// /// Gets or sets a value indicating whether [disable check for updates]. /// public bool DisableCheckForUpdates { get { return _disableCheckForUpdates; } set { _disableCheckForUpdates = value; RaisePropertyChangedAuto(); } } private IDiagnosticsFrameProvider _diagnosticsFrameProvider; /// /// Gets or sets the diagnostics frame provider. /// public IDiagnosticsFrameProvider DiagnosticsFrameProvider { get { return _diagnosticsFrameProvider; } set { _diagnosticsFrameProvider = value; RaisePropertyChangedAuto(); } } /// /// Initializes a new instance of the class. /// /// The view. /// The authentication provider. /// The studio module loader. /// The notification provider. /// The application manager. /// The navigation manager. public MainViewVM( IAuthenticationProvider authenticationProvider, IStudioModuleLoader studioModuleLoader, INotificationProvider notificationProvider, IStudioApplicationManager applicationManager, INavigationManager navigationManager, IEventLogger eventLogger, IDiagnosticsFrameProvider frameProvider, ISpeechProvider speechProvider, TeamFoundationServiceExtendedClient tfs) : base() { TFSClient = tfs; _eventLogger = eventLogger; _navigation = navigationManager; AuthenticationProvider = authenticationProvider; StudioModuleLoader = studioModuleLoader; NotificationProvider = notificationProvider; ApplicationManager = applicationManager; DiagnosticsFrameProvider = frameProvider; SpeechProvider = speechProvider; _settings = SettingsManager.Default.GetOrCreate(); StartModuleCommand = new RelayCommand(StartModule); HomeCommand = new RelayCommand(Home); ConnectCommand = new RelayCommand(ConnectToMachine); SignoutCommand = new RelayCommand(SignOut); DisconnectCommand = new RelayCommand(DisconnectFromMachine, (x) => ApplicationManager.IsMachineConnected && !_isDisconnecting); OpenModuleInWindowCommand = new RelayCommand((x) => { OpenModuleInWindow(x); }); ExitCommand = new RelayCommand(ExitApplication); UpdateCenterCommand = new RelayCommand(NavigateToUpdateCenter); _updateCheckThread = new Thread(UpdateCheckThreadMethod); _updateCheckThread.IsBackground = true; _updateCheckThread.Start(); ApplicationManager.ConnectedMachineChanged += (sender, machine) => { if (machine != null) { machine.MachineEventsStateProvider.NewEvents -= MachineEventsStateProvider_NewEvents; machine.MachineEventsStateProvider.NewEvents += MachineEventsStateProvider_NewEvents; machine.MachineEventsStateProvider.EventsResolved -= MachineEventsStateProvider_EventsResolved; machine.MachineEventsStateProvider.EventsResolved += MachineEventsStateProvider_EventsResolved; } }; ToggleSpeechCommand = new RelayCommand(() => { SpeechProvider.Mute = !SpeechProvider.Mute; }); ResolveMachineEventCommand = new RelayCommand(ResolveMachineEvent); ReportIssueCommand = new RelayCommand(ReportIssue); OpenResolvedBugsCommand = new RelayCommand(OpenResolvedBugs); OpenDeveloperConsoleCommand = new RelayCommand(OpenDeveloperConsole); TangoMessenger.Default.Register((x) => DisableCheckForUpdates = true); AboutCommand = new RelayCommand(ShowAboutDialog); } private void MachineEventsStateProvider_EventsResolved(object sender, IEnumerable e) { if (ApplicationManager.ConnectedMachine.MachineEventsStateProvider.Events.Count == 0) { IsMachineErrorsOpened = false; } } private void MachineEventsStateProvider_NewEvents(object sender, IEnumerable e) { IsMachineErrorsOpened = true; } private void UpdateCheckThreadMethod() { while (!DisableCheckForUpdates) { Thread.Sleep(TimeSpan.FromMinutes(0.2)); try { if (_authenticationProvider.CurrentUser != null) { var client = new MachineStudioUpdateService(); CheckForUpdatesResponse response = client.CheckForUpdates(new CheckForUpdatesRequest() { AccessToken = _authenticationProvider.AccessToken, Version = _applicationManager.Version.ToString(), AcceptBetaRelease = _settings.AcceptBetaRelease, }).Result; IsUpdateAvailable = response.IsUpdateAvailable; LatestVersion = response.Version; } } catch (Exception ex) { LogManager.Log(ex, "Error in version update periodic check..."); } Thread.Sleep(TimeSpan.FromMinutes(4)); } } /// /// Disconnected from the current connected machine. /// private async void DisconnectFromMachine() { using (_notificationProvider.PushTaskItem("Disconnecting from machine...")) { try { _isDisconnecting = true; InvalidateRelayCommands(); String serial = ApplicationManager.ConnectedMachine.SerialNumber; await ApplicationManager.ConnectedMachine.Disconnect(); ApplicationManager.SetConnectedMachine(null); _eventLogger.Log("Disconnected from machine " + serial); PostMessage(new MachineConnectionChangedMessage() { Machine = null }); } catch (Exception ex) { _eventLogger.Log(ex, "Error disconnecting from machine."); LogManager.Log(ex, "Could not disconnect from machine."); } finally { _isDisconnecting = false; InvalidateRelayCommands(); } } } /// /// Signs-out the current logged-in user. /// private void SignOut() { _authenticationProvider.Logout(); Home(); _navigation.NavigateTo(NavigationView.LoginView); CurrentModule = null; IsMenuOpened = false; } /// /// Opens the machine connection dialog to allow the user to scan and connect to remote machines view USB/TCP. /// private void ConnectToMachine() { if (ApplicationManager.ConnectedMachine == null) { _notificationProvider.ShowModalDialog(async (x) => { if (x.SelectedMachine != null) { if (ApplicationManager.IsMachineConnected) { using (_notificationProvider.PushTaskItem("Disconnecting...")) { await ApplicationManager.ConnectedMachine.Disconnect(); await Task.Delay(1000); } } x.SelectedMachine.EnableDiagnostics = x.EnableDiagnostics; x.SelectedMachine.EnableEmbeddedDebugging = x.EnableDiagnostics; x.SelectedMachine.EnableEventsNotification = x.EnableDiagnostics; x.SelectedMachine.UseKeepAlive = x.EnableKeepAlive; if (x.SelectedMachine.RequiresAuthentication) { //Check machine exist on my database first if (x.SelectedMachine.Machine == null) { _notificationProvider.ShowError($"The specified machine '{x.SelectedMachine.SerialNumber}' could not be found on the database. Aborting connection."); return; } _notificationProvider.ShowModalDialog(async (login) => { using (NotificationProvider.PushTaskItem("Connecting to machine " + x.SelectedMachine.ToString() + "...")) { try { await x.SelectedMachine.As().Connect(new PMR.Integration.ExternalBridgeLoginRequest() { AppID = "Machine Studio", HostName = Environment.MachineName, Password = login.Password, UserGuid = AuthenticationProvider.CurrentUser.Guid, Intent = PMR.Integration.ExternalBridgeLoginIntent.Override, }); ApplicationManager.SetConnectedMachine(x.SelectedMachine); (x.SelectedMachine as IExternalBridgeSecureClient).SessionClosed += (_, __) => { InvokeUI(() => { _notificationProvider.ShowError("The remote machine has closed the current session. Machine disconnected."); ApplicationManager.SetConnectedMachine(null); }); }; PostMessage(new MachineConnectionChangedMessage() { Machine = x.SelectedMachine }); _eventLogger.Log(String.Format("Successfully connected to machine {0} via TCP", x.SelectedMachine.SerialNumber)); if (x.UploadHardwareConfiguration) { UploadHardwareConfiguration(); } } catch (ResponseErrorException ex) { LogManager.Log(ex); _eventLogger.Log(ex, "Error connecting to machine " + x.SelectedMachine.SerialNumber); _notificationProvider.ShowError("Could not connect to the selected machine." + Environment.NewLine + ex.Container.ErrorMessage); } catch (Exception ex) { LogManager.Log(ex); _eventLogger.Log(ex, "Error connecting to machine " + x.SelectedMachine.SerialNumber); _notificationProvider.ShowError("Could not connect to the selected machine." + Environment.NewLine + ex.Message); } InvalidateRelayCommands(); } }); } else { _notificationProvider.ShowModalDialog(async (vm) => { if (vm.SelectedMachine != null) { using (NotificationProvider.PushTaskItem("Connecting to " + x.SelectedMachine.ToString() + "...")) { try { await x.SelectedMachine.Connect(); x.SelectedMachine.SerialNumber = vm.SelectedMachine.SerialNumber; ApplicationManager.SetConnectedMachine(x.SelectedMachine); PostMessage(new MachineConnectionChangedMessage() { Machine = x.SelectedMachine }); _eventLogger.Log(String.Format("Successfully connected to machine {0} via USB", x.SelectedMachine.SerialNumber)); _settings.LastVirtualMachineSerialNumber = vm.SelectedMachine.SerialNumber; _settings.Save(); if (x.UploadHardwareConfiguration) { UploadHardwareConfiguration(); } } catch (Exception ex) { LogManager.Log(ex); if (x.SelectedMachine != null) { _eventLogger.Log(ex, "Error connecting to machine " + x.SelectedMachine.SerialNumber); } _notificationProvider.ShowError("Could not connect to the selected machine." + Environment.NewLine + ex.Message); } InvalidateRelayCommands(); } } }); } InvalidateRelayCommands(); } InvalidateRelayCommands(); }); } else { _notificationProvider.ShowModalDialog(async (x) => { if (x.Result == ConnectedMachineViewVM.ConnectedMachineVMResult.Disconnect) { DisconnectFromMachine(); } else if (x.Result == ConnectedMachineViewVM.ConnectedMachineVMResult.UploadHardwareConfig) { if (NotificationProvider.ShowQuestion("This will reset the machine hardware configuration to the database configuration. Are you sure?")) { using (NotificationProvider.PushTaskItem("Uploading hardware configuration...")) { UploadHardwareConfiguration(); } } } else if (x.Result == ConnectedMachineViewVM.ConnectedMachineVMResult.Reset) { if (NotificationProvider.ShowQuestion("This will reset the embedded device. Are you sure?")) { using (NotificationProvider.PushTaskItem("Resetting the embedded device...")) { try { await ApplicationManager.ConnectedMachine.Reset(); NotificationProvider.ShowInfo("Embedded device has been restarted."); } catch (Exception ex) { LogManager.Log(ex, "Error resetting embedded device."); NotificationProvider.ShowError("Error resetting embedded device." + Environment.NewLine + ex.Message); } } } } else if (x.Result == ConnectedMachineViewVM.ConnectedMachineVMResult.TurnOffHeaters) { if (NotificationProvider.ShowQuestion("This will reset the process parameters. Are you sure?")) { using (NotificationProvider.PushTaskItem("Resetting process parameters...")) { try { await ApplicationManager.ConnectedMachine.UploadProcessParameters(new ProcessParametersTable()); NotificationProvider.ShowInfo("Heaters are turned off."); } catch (Exception ex) { LogManager.Log(ex, "Error resetting process parameters."); NotificationProvider.ShowError("Error resetting process parameters." + Environment.NewLine + ex.Message); } } } } }); } InvalidateRelayCommands(); } private async void UploadHardwareConfiguration() { try { using (ObservablesContext db = ObservablesContext.CreateDefault()) { var config = db.Adapter.GetConfiguration(s => s.Guid == ApplicationManager.Machine.ConfigurationGuid); var hw = db.Adapter.GetHardwareVersionByMachine(ApplicationManager.Machine.Guid); await ApplicationManager.ConnectedMachine.UploadHardwareConfiguration(hw, config); } NotificationProvider.ShowInfo("Hardware configuration uploaded successfully."); } catch (Exception ex) { LogManager.Log(ex, "Error uploading hardware configuration."); NotificationProvider.ShowError("Error uploading hardware configuration." + Environment.NewLine + ex.Message); } } /// /// Navigates to the home screen. /// private void Home() { StartModule(null); } /// /// Starts the specified module. /// /// The module. internal void StartModule(IStudioModule module) { IsMenuOpened = false; if ((_applicationManager as DefaultStudioApplicationManager).IsModuleInNewWindow(module)) return; if (module != null) { LogManager.Log(String.Format("Starting module '{0}'...", module.Name)); if (!(MainView.Self as MainView).NavigationControl.Elements.ToList().Exists(x => x.GetType() == module.MainViewType)) { LogManager.Log("Module was not initialized. Initializing..."); FrameworkElement view = Activator.CreateInstance(module.MainViewType) as FrameworkElement; NavigationControl.SetNavigationName(view, module.Name); (MainView.Self as MainView).NavigationControl.Elements.Add(view); } } foreach (var m in StudioModuleLoader.AllModules.Where(x => x != module && !x.InNewWindow)) { m.IsLoaded = false; } CurrentModule = module; if (module != null) { CurrentModule.IsLoaded = true; IsModuleLoaded = true; LogManager.Log(String.Format("Navigating to module '{0}'...", module.Name)); (MainView.Self as MainView).NavigationControl.NavigateTo(module.Name); } else { IsModuleLoaded = false; LogManager.Log(String.Format("Navigating to Home...")); (MainView.Self as MainView).NavigationControl.NavigateTo("Home"); } } /// /// Opens the module in a new window. /// /// The module. private void OpenModuleInWindow(IStudioModule module, Rect? bounds = null, WindowState? state = null) { if (module == null) return; try { module.InNewWindow = true; StartModule(null); LogManager.Log(String.Format("Starting module '{0}' in new window...", module.Name)); if (!(MainView.Self as MainView).NavigationControl.Elements.ToList().Exists(x => x.GetType() == module.MainViewType)) { LogManager.Log("Module was not initialized. Initializing..."); FrameworkElement v = Activator.CreateInstance(module.MainViewType) as FrameworkElement; NavigationControl.SetNavigationName(v, module.Name); (MainView.Self as MainView).NavigationControl.Elements.Add(v); } LogManager.Log("Detaching module view..."); var view = (MainView.Self as MainView).NavigationControl.GetAndDetach(module.Name); ModuleWindowVM vm = new ModuleWindowVM(module); ModuleWindow window = new ModuleWindow(this, vm, view); if (bounds.HasValue) { window.WindowStartupLocation = WindowStartupLocation.Manual; window.WindowState = WindowState.Normal; window.Left = bounds.Value.Left; window.Top = bounds.Value.Top; window.Width = bounds.Value.Width; window.Height = bounds.Value.Height; window.Loaded += (_, __) => { window.WindowState = state.Value; }; } window.Closing += (x, y) => { LogManager.Log(String.Format("Closing module '{0}' on new window...", module.Name)); window.grid.Children.Remove(view); module.InNewWindow = false; }; window.Owner = MainWindow.Instance; LogManager.Log("Opening new window..."); window.Show(); (_applicationManager as DefaultStudioApplicationManager).RegisterOpenedWindow(window); } catch (Exception ex) { LogManager.Log(ex, "Error popping out module " + module.Name); _notificationProvider.ShowError("Error popping out module " + module.Name); } } /// /// Navigates to update center. /// private void NavigateToUpdateCenter() { _navigation.NavigateTo(NavigationView.UpdateView); } /// /// Exits the application. /// private void ExitApplication() { _applicationManager.ShutDown(); } /// /// Displays the HTML. /// /// The HTML page. private async void ResolveMachineEvent(MachinesEvent machineEvent) { } /// /// Shows the about dialog. /// private void ShowAboutDialog() { NotificationProvider.ShowModalDialog((x) => { }); } private void ReportIssue() { Tango.TFS.WorkItem bug = null; try { bug = TFSClient.CreateBug(); } catch (Exception ex) { _notificationProvider.ShowError(ex.Message); return; } _notificationProvider.ShowModalDialog(new ReportIssueViewVM(TFSClient.Project, bug), async (vm) => { using (_notificationProvider.PushTaskItem("Uploading bug report...")) { try { TFSClient.FinalizeBug(vm.WorkItem); await TFSClient.UploadWorkItem(vm.WorkItem); } catch (Exception ex) { LogManager.Log(ex, "Error while trying to upload bug report."); _notificationProvider.ShowError("An error occurred while trying to create the issue." + Environment.NewLine + ex.Message); } } }, null); } private void OpenResolvedBugs() { ResolvedIssuesViewVM vm = null; vm = new ResolvedIssuesViewVM(TFSClient, async (item) => { //Approve using (_notificationProvider.PushTaskItem("Approving issue...")) { vm.IsAvailable = false; try { await TFSClient.CloseWorkItem(item); } catch (Exception ex) { LogManager.Log(ex, $"Error while trying to approve resolved bug '{item.ID}'."); _notificationProvider.ShowError("An error occurred while trying to update the issue." + Environment.NewLine + ex.Message); } vm.IsAvailable = true; if (TFSClient.ResolvedWorkItems.Count == 0) { vm.Close(); } } }, async (item) => { //Decline using (_notificationProvider.PushTaskItem("Reactivating issue...")) { vm.IsAvailable = false; try { await TFSClient.ReactivateWorkItem(item); } catch (Exception ex) { LogManager.Log(ex, $"Error while trying to decline resolved bug '{item.ID}'."); _notificationProvider.ShowError("An error occurred while trying to update the issue." + Environment.NewLine + ex.Message); } vm.IsAvailable = true; if (TFSClient.ResolvedWorkItems.Count == 0) { vm.Close(); } } }); _notificationProvider.ShowModalDialog(vm, (_) => { }, null); } private void OpenDeveloperConsole() { Console.ConsoleWindow console = new Console.ConsoleWindow(); ApplicationManager.RegisterOpenedWindow(console); console.Owner = MainWindow.Instance; console.Show(); } public override void OnApplicationReady() { if (!IsApplicationReady) { var settings = SettingsManager.Default.GetOrCreate(); foreach (var item in settings.StudioModulesBounds) { var module = StudioModuleLoader.AllModules.SingleOrDefault(x => x.Name == item.Name); if (module != null && !module.InNewWindow) { OpenModuleInWindow(module, item.Bounds, item.State); } } if (settings.LastMainModuleName != null) { var m = StudioModuleLoader.UserModules.SingleOrDefault(x => x.Name == settings.LastMainModuleName); if (m != null) { StartModule(m); } } IsApplicationReady = true; } } } }