www.gusucode.com > robotsimulink 工具箱 matlab源码程序 > robotsimulink/robotslros/+robotics/+slros/+internal/+diag/DeviceDiagnostics.m
classdef DeviceDiagnostics < handle %This class is for internal use only. It may be removed in the future. %DeviceDiagnostics Helper class for running diagnostics on device parameter settings % This class can be invoked in a MATLAB and a Simulink context. % % When invoked from MATLAB, construct the object without the modelName % input. All errors, warnings, and high-priority warnings are % displayed on the MATLAB command line. % % When invoked from Simulink, construct the object with the modelName % input. All errors, warnings, and high-priority warnings are % displayed in the Simulink Diagnostic Viewer. % % % Examples: % % Run a diagnostic test % tester = robotics.slros.internal.diag.DeviceDiagnostics(modelName); % tester.runDiagnostics(hostname, sshPort, username, password, rosFolder, catkinWs); % % % Use the diagnostic object without running a full test suite % tester = robotics.slros.internal.diag.DeviceDiagnostics(modelName); % tester.connect(hostname, sshPort, username, password); % rosFolder = tester.recoverROSFolderFromCatkinWorkspace(catkinWs); % Copyright 2016 The MathWorks, Inc. properties (Dependent) %RunMode - The run mode for the device diagnostics % Possible values: % 'test' - For testing purposes, print out all information, % warnings, and errors. % 'build' - At build time, do not print information and convert % all high-priority warnings to errors. The user did % not request to run the model. % 'buildrun' - At build time, do not print information and convert % all high-priority warnings to errors. The user % requested to run the model. % % Default: 'test' RunMode end properties (SetAccess = private) %ModelName - The Simulink model associated with this ModelName %DiagnosticStream - The object streaming messages to an output DiagnosticStream end properties (Constant) %StepSeparator - Separator in output between diagnostic steps StepSeparator = '---' end properties (Constant, Access = ?matlab.unittest.TestCase) %ValidRunModes - Valid strings for the RunMode property ValidRunModes = [robotics.slros.internal.diag.IDiagnosticStream.ValidRunModes, ... 'buildrun'] end properties (Access = ?matlab.unittest.TestCase) %StandardROSDistributions - List of standard ROS distributions that we search for StandardROSDistributions = {'hydro', 'indigo', 'jade'} %StandardInstallRoot - Root folder for standard ROS installation StandardInstallRoot = '/opt/ros' %SSHClient - The SSH client. It needs to be initialized via connectToDevice SSHClient %InternalRunMode - Internal storage for the run mode InternalRunMode = 'test' end properties (Dependent, Access = private) %IsSimulinkContext - Are the diagnostics invoked from Simulink context? % This is just a convenience wrapper around the ModelName IsSimulinkContext end %% Main public interface methods function obj = DeviceDiagnostics(modelName) %DeviceDiagnostics Constructor narginchk(0,1) if nargin > 0 validateattributes(modelName, {'char'}, {}, 'DeviceDiagnostics', 'modelName'); else modelName = ''; end if ~isempty(modelName) % Invoked from Simulink context. Use diagnostic viewer output. % Note that the modelName has to be non-empty to avoid % problems with obj.DiagnosticStream. obj.ModelName = modelName; % Use diagnostic viewer obj.DiagnosticStream = robotics.slros.internal.diag.DiagnosticViewerStream(modelName, true); else % Invoked from MATLAB context. Use command-line output. obj.ModelName = ''; % Use command-line stream obj.DiagnosticStream = robotics.slros.internal.diag.CommandLineStream; end end function hasErrors = runDiagnostics(obj, deviceAddress, sshPort, username, password, rosFolder, catkinWs) %runDiagnostics Run the diagnostics on the ROS device % Synchronously execute the diagnostic run hasErrors = obj.runDiagnosticsImpl(deviceAddress, sshPort, username, password, rosFolder, catkinWs); % For asynchronous execution of the diagnostic run, consider % executing a timer call instead. %robotics_tests.ROSUtils.scheduleTimerCallback(0.1, ... % @() obj.runDeviceDiagnosticsImpl(deviceAddress, username, password, rosFolder, catkinWs)) end function sshClient = connect(obj, hostname, sshPort, username, password) %connect Connect to the ROS device with the given settings % CONNECT(OBJ, HOSTNAME, SSHPORT, USERNAME, PASSWORD) % connects to the ROS device with the given parameters % % CONNECT(OBJ, SSHCLIENT) uses the initialized SSHCLIENT % object to connect to the target device. % % This function does two things: % 1. It initializes the internal SSHClient object % 2. It returns a handle to the SSHClient object if isa(hostname, 'robotics.codertarget.internal.ssh2client') % Syntax: CONNECT(OBJ, SSHCLIENT) obj.SSHClient = hostname; else % Syntax: CONNECT(OBJ, HOSTNAME, SSHPORT, USERNAME, PASSWORD) obj.SSHClient = robotics.codertarget.internal.ssh2client(hostname, username, password, sshPort); end sshClient = obj.SSHClient; end function isSimulink = get.IsSimulinkContext(obj) isSimulink = ~isempty(obj.ModelName); end end %% Property getters and setters methods function runMode = get.RunMode(obj) %get.RunMode Custom getter for dependent RunMode property runMode = obj.InternalRunMode; end function set.RunMode(obj, runMode) %set.RunMode Custom setter for dependent RunMode property validateattributes(runMode, {'char'}, {'nonempty','row'}, 'DeviceDiagnostics', 'RunMode'); validRunMode = validatestring(runMode, obj.ValidRunModes, ... 'DeviceDiagnostics', 'RunMode'); obj.InternalRunMode = validRunMode; % Adjust the diagnostic stream behavior switch validRunMode case 'test' obj.DiagnosticStream.RunMode = 'test'; case {'build', 'buildrun'} obj.DiagnosticStream.RunMode = 'build'; end end end %% Additional public methods methods function [hasSudo, requiresPassword] = hasSudoAccess(obj, password) %hasSudoAccess Determines if a user has sudo access % HASSUDO is TRUE if the user has sudo access and FALSE % otherwise. If HASSUDO is TRUE, then REQUIRESPASSWORD % indicates if sudo calls require a password or not. If % REQUIRESPASSWORD is FALSE, it is password-less sudo. % Initialize default return values hasSudo = false; requiresPassword = true; % Try password-less sudo first (-n means non-interactive sudo) try obj.SSHClient.execute('sudo -n true'); hasSudo = true; requiresPassword = false; return; catch % Did not work end % Try sudo with password try obj.SSHClient.execute(['echo ' password '| sudo -S true']); hasSudo = true; requiresPassword = true; return; catch % This did not work either. The user is probably not part % of the sudoer list. end end function folderExists = doesFolderExist(obj, folderName) %doesFolderExist Checks if folder with given name exists % Returns TRUE if folder exists and FALSE otherwise. try % Use the '-d' option to check for directory existence obj.SSHClient.execute(['[ -d ' obj.handleSpaces(folderName) ' ]']); % If the command executes successfully, the folder exists folderExists = true; catch ex % If something unexpected goes wrong, e.g. SSH connection fails, % rethrow the associated exception if ~strcmp(ex.identifier, 'utils:sshclient:system') rethrow(ex); end % Directory does not exist folderExists = false; end end function fileExists = doesFileExist(obj, fileName) %doesFileExist Checks if file with given name exists % Returns TRUE if file exists and FALSE otherwise. try % Use the '-f' option to check for existence of regular files obj.SSHClient.execute(['[ -f ' obj.handleSpaces(fileName) ' ]']); % If the command executes successfully, the folder exists fileExists = true; catch ex % If something unexpected goes wrong, e.g. SSH connection fails, % rethrow the associated exception if ~strcmp(ex.identifier, 'utils:sshclient:system') rethrow(ex); end % File does not exist if exception 'utils:sshclient:system' is thrown fileExists = false; end end function fileWritable = isFileWritable(obj, fileName) %isFileWritable Checks if file/folder with given name is writable by the user % Returns TRUE if file/folder is writable and FALSE otherwise. % Note that this function works for both files as well as % folders. try % Call with the '-w' option to check if file/folder is % writable obj.SSHClient.execute(['[ -w ' obj.handleSpaces(fileName) ' ]']); % If the command executes successfully, the folder exists fileWritable = true; catch ex % If something unexpected goes wrong, e.g. SSH connection fails, % rethrow the associated exception if ~strcmp(ex.identifier, 'utils:sshclient:system') rethrow(ex); end % File does not exist fileWritable = false; end end function escapedPath = handleSpaces(~, filePath) %handleSpaces Handle spaces in file or folder path % In the system command that we send over SSH, spaces in the % path to a file or a folder need to be escaped with a backslash. escapedPath = strrep(filePath, ' ', '\ '); % If the spaces were already escaped, we should undo the % double-escaping escapedPath = strrep(escapedPath, '\\ ', '\ '); end function retValue = safeSSHExecute(obj, command) %safeSSHExecute Safe execution of SSH command % Returns empty if the command execution triggered an error. try retValue = obj.SSHClient.execute(command); catch retValue = ''; end end function isFolderValid = isROSFolderValid(obj, rosFolder) %isROSFolderValid Check if given folder contains a ROS distribution % This is a simple test that only checks if the setup.bash % file exists in the right place. % The existence test can handle multiple forward slashes, % so it is safe to concatenate. isFolderValid = obj.doesFolderExist(rosFolder) && ... obj.doesFileExist([rosFolder '/setup.bash']); end function isWsValid = isCatkinWorkspaceValid(obj, catkinWs) %isCatkinWorkspaceValid Check if given folder contains a valid Catkin workspace % The user creates the Catkin workspace with % catkin_init_workspace inside the <workspace_root>/src % folder. This will create the src/CMakeLists.txt file. Check % for the existence of that file. % We also enforce that the user called catkin_make on top of % the workspace, since we rely on the "setup.bash" sourcing % at deployment time. isWsValid = obj.doesFileExist([catkinWs '/src/CMakeLists.txt']) && ... obj.doesFileExist([catkinWs '/devel/setup.bash']); end function rosFolder = recoverROSFolderFromCatkinWorkspace(obj, catkinWs) %recoverROSFolderFromCatkinWorkspace Get ROS base folder from Catkin workspace % Each Catkin workspace is linked to its ROS installation and % we can recover the ROS base folder % This function returns an empty string if the operation is unsuccessful. % This only works if the Catkin workspace is valid setupUtilFile = obj.handleSpaces([catkinWs '/devel/_setup_util.py']); % The Python script contains a line similar to this ("indigo" being the ROS distro): % CMAKE_PREFIX_PATH = '/opt/ros/indigo'.split(';') % Extract the ROS path by calling sed with a regular expression. rosFolder = obj.safeSSHExecute( ... ['sed -n ''s/.*CMAKE_PREFIX_PATH\s=\s''\''''\(.*\)''\''''.split.*/\1/p'' ' setupUtilFile]); rosFolder = strtrim(rosFolder); end function rosFolders = recoverROSFolderFromStandardLocations(obj) %recoverROSFolderFromStandardLocations Search standard folders for ROS installations % Typically, ROS is installed under /opt/ros/* % This function returns a cell array of ROS folders, as there % might be multiple matches (if multiple ROS distributions % are installed). rosFolders = {}; for i = 1:length(obj.StandardROSDistributions) dist = obj.StandardROSDistributions{i}; folderCandidate = [obj.StandardInstallRoot '/' dist]; if obj.doesFolderExist(folderCandidate) rosFolders{end+1} = folderCandidate; %#ok<AGROW> end end end function distName = getROSDistribution(obj, rosFolder) %getROSDistribution Get official ROS distribution name from ROS base folder % This function returns an empty string if the operation is unsuccessful. % Recover distribution name from profile file profileFile = obj.handleSpaces([rosFolder '/etc/catkin/profile.d/10.ros.sh']); % The profile file contains a line similar to this: % export ROS_DISTRO=indigo distName = obj.safeSSHExecute(['sed -n ''s/.*ROS_DISTRO=//p'' ' profileFile]); distName = strtrim(distName); end function createFolder(obj, folderPath) %createFolder Create a folder at the given path % All intermediate folders will also be created. if ~obj.doesFolderExist(folderPath) obj.SSHClient.execute(['mkdir -p ' obj.handleSpaces(folderPath)]); end end function createCatkinWorkspace(obj, rosFolder, catkinWs) %createCatkinWorkspace Create a Catkin workspace in a given folder % If the folder does not exist yet, it will be created. catkinWs = obj.handleSpaces(catkinWs); catkinSrcDir = [catkinWs '/src']; rosFolder = obj.handleSpaces(rosFolder); % Create folder if it does not exist yet obj.createFolder(catkinSrcDir); % Initialize the Catkin workspace. Note that you have to % execute the source call, catkin_init_workspace, and catkin_make % in one system command. Otherwise, the environment variables are not % persistent. cmd = ['source ' rosFolder '/setup.bash' ... ';' ... 'catkin_init_workspace ' catkinSrcDir]; try obj.SSHClient.execute(cmd); catch % This error might be okay if the Catkin workspace has % already been initialized, but catkin_make has not been % called. % Continue for now. end cmd = ['source ' rosFolder '/setup.bash' ... ';' ... 'catkin_make -C ' catkinWs]; obj.SSHClient.execute(cmd); end function taskingMode = modelTaskingMode(obj) %modelTaskingMode Return tasking mode for Simulink model % There are two possible tasking modes: single-tasking and % multi-tasking. Simulink also has an "Auto" mode. If the % diagnostics are executed during build, we can resolve the % "Auto" to the actual selected tasking mode. % % TASKINGMODE is one of {'SingleTasking', 'MultiTasking', % 'Auto', ''}. If the tasking mode cannot be determined or if % the diagnostics run in the MATLAB context return ''. taskingMode = ''; if ~obj.IsSimulinkContext return; end % Get the solver mode setting from the current model name try acs = getActiveConfigSet(obj.ModelName); taskingMode = getProp(acs, 'SolverMode'); catch % Swallow the exception. This could happen if the model % name is no longer valid. end if isequal(obj.RunMode, 'test') return; end % If diagnostics are in build mode, then we can resolve the % 'Auto' tasking mode of the current model. % Typically, this check is expensive, since the model has be % updated, but since we are in build mode, the model update % already succeeded, so we can resolve 'Auto' to the actual % mode. try if strcmpi(taskingMode, 'auto') if checkSingleTaskingSolver({obj.ModelName}) taskingMode = 'SingleTasking'; else taskingMode = 'MultiTasking'; end end catch % Swallow the exception. This could happen if the model % name is not valid. end end end %% Private methods that are used during the diagnostic run methods (Access = private) function hasErrors = runDiagnosticsImpl(obj, hostname, sshPort, username, password, rosFolder, catkinWs) %runDiagnosticsImpl Run diagnostics on the given device parameters runName = message('robotics:robotslros:devicediag:DiagRunName').getString; obj.DiagnosticStream.open(runName); % Close diagnostic stream when function exits or errors closeStream = onCleanup(@() obj.DiagnosticStream.close); % Try to ping device obj.pingDevice(hostname); % Try to connect SSH client obj.DiagnosticStream.reportInfo(obj.StepSeparator); sshClient = obj.connectToDevice(hostname, sshPort, username, password); if isempty(sshClient) hasErrors = true; return; end % Testing user privileges obj.DiagnosticStream.reportInfo(obj.StepSeparator); obj.userPrivileges(username, password); % Testing ROS distribution obj.DiagnosticStream.reportInfo(obj.StepSeparator); obj.detectROSDistribution(hostname, sshPort, username, password, rosFolder, catkinWs); % Testing Catkin workspace obj.DiagnosticStream.reportInfo(obj.StepSeparator); obj.checkCatkinWorkspace(hostname, sshPort, username, password, rosFolder, catkinWs); % Disconnect from the target obj.DiagnosticStream.reportInfo(obj.StepSeparator); obj.DiagnosticStream.reportInfo(['6. ' message('robotics:robotslros:devicediag:Disconnect', hostname).getString]); obj.DiagnosticStream.reportInfo(message('robotics:robotslros:devicediag:DiagnosticsDone').getString); % Shut down SSH connection sshClient.delete; hasErrors = obj.DiagnosticStream.ErrorCount > 0; end function pingDevice(obj, hostname) %pingDevice Test if the device can be pinged obj.DiagnosticStream.reportInfo(['1. ' message('robotics:robotslros:devicediag:PingDevice', hostname).getString]); if ispc cmd = ['ping -n 1 ' hostname]; else cmd = ['ping -c 1 ' hostname]; end [st, msg] = system(cmd); if (st == 0) && ~isempty(regexpi(msg, '\sTTL=')) obj.DiagnosticStream.reportInfo(message('robotics:robotslros:devicediag:PingDeviceSuccess').getString); else % If ping does not succeed, show a warning. It is likely % that subsequent operations will fail. obj.DiagnosticStream.reportHighPriorityWarning(message('robotics:robotslros:devicediag:PingDeviceFailure')); end end function sshClient = connectToDevice(obj, hostname, sshPort, username, password) %connectToDevice Connect to device over SSH % The test will display an error and abort if it cannot % connect to the device. obj.DiagnosticStream.reportInfo(['2. ' message('robotics:robotslros:devicediag:ConnectDevice', hostname, sshPort, username).getString]); try sshClient = obj.connect(hostname, sshPort, username, password); obj.DiagnosticStream.reportInfo(message('robotics:robotslros:devicediag:ConnectDeviceSuccess').getString); catch % A connection failure is a fatal error sshClient = []; obj.DiagnosticStream.reportError(message('robotics:robotslros:devicediag:ConnectDeviceFailure')); end end function userPrivileges(obj, username, password) %userPrivileges Confirm if user has admin privileges or not obj.DiagnosticStream.reportInfo(['3. ' message('robotics:robotslros:devicediag:SudoCheck', username).getString]); [hasSudo, requiresPassword] = obj.hasSudoAccess(password); if hasSudo if requiresPassword sudoMsg = message('robotics:robotslros:devicediag:SudoWithPassword').getString; else sudoMsg = message('robotics:robotslros:devicediag:SudoWithoutPassword').getString; end % Report positive result to user and return obj.DiagnosticStream.reportInfo(message('robotics:robotslros:devicediag:SudoCheckSuccess', sudoMsg).getString); return; end % The sudo call failed, so user has no admin rights % Determine the current model's tasking mode taskingMode = obj.modelTaskingMode; diag = message('robotics:robotslros:devicediag:SudoCheckFailure', obj.ModelName, taskingMode); % If tasking mode is 'MultiTasking', the severity is dependent % on the run mode. If not, use a standard warning. if strcmpi(taskingMode, 'multitasking') switch lower(obj.RunMode) case 'build' % If the user does not want to run the model, this % is only a warning. obj.DiagnosticStream.reportWarning(diag); case 'buildrun' % If the user wants to run the model, this % is a high-priority warning. obj.DiagnosticStream.reportHighPriorityWarning(diag); otherwise obj.DiagnosticStream.reportHighPriorityWarning(diag); end else obj.DiagnosticStream.reportWarning(diag); end end function detectROSDistribution(obj, hostname, sshPort, username, password, rosFolder, catkinWs) %detectROSDistribution Checking the validity of ROS distribution folder obj.DiagnosticStream.reportInfo(['4. ' message('robotics:robotslros:devicediag:ROSFolder').getString]); % Check first if the folder exists and if so, if it contains a % ROS installation. isFolderValid = false; if ~isempty(rosFolder) obj.DiagnosticStream.reportInfo(message('robotics:robotslros:devicediag:ROSFolderExist', rosFolder).getString); isFolderValid = obj.isROSFolderValid(rosFolder); end if isFolderValid % Best case: Folder contains ROS. Recover distribution name % and return. distName = obj.getROSDistribution(rosFolder); obj.DiagnosticStream.reportInfo(message('robotics:robotslros:devicediag:ROSFolderExistSuccess', upper(distName), rosFolder).getString); return end % Folder is not valid or does not contain a ROS distribution. % Give the user some options to recover the correct folder. if isempty(catkinWs) % No Catkin workspace is given, so that restricts the FixIt % options. obj.DiagnosticStream.reportHighPriorityWarning(message('robotics:robotslros:devicediag:ROSFolderExistFailureNoCatkin', ... rosFolder, hostname, sshPort, username, password)); else % With a Catkin workspace, we have another option for % recovering the ROS installation folder obj.DiagnosticStream.reportHighPriorityWarning(message('robotics:robotslros:devicediag:ROSFolderExistFailure', ... rosFolder, hostname, sshPort, username, password, catkinWs)); end end function checkCatkinWorkspace(obj, hostname, sshPort, username, password, rosFolder, catkinWs) %checkCatkinWorkspace Check the validity of the Catkin workspace obj.DiagnosticStream.reportInfo(['5. ' message('robotics:robotslros:devicediag:CatkinWs', catkinWs).getString]); % Check if folder exists if ~obj.doesFolderExist(catkinWs) % If not, give the user the option to create it if obj.isROSFolderValid(rosFolder) % If we have a valid ROS folder, we can create the % folder AND initialize the Catkin workspace diag = message('robotics:robotslros:devicediag:FolderDoesNotExistWithCatkin', catkinWs, hostname, sshPort, username, password, rosFolder); else % We can only create the folder, since we do not know % where ROS is installed. diag = message('robotics:robotslros:devicediag:FolderDoesNotExistNoCatkin', catkinWs, hostname, sshPort, username, password); end obj.DiagnosticStream.reportHighPriorityWarning(diag); % No point in continuing. Return. return; end % Folder exists obj.DiagnosticStream.reportInfo(message('robotics:robotslros:devicediag:FolderExists').getString); % Test if folder is writable if ~obj.isFileWritable(catkinWs) % If not, show a warning and return. obj.DiagnosticStream.reportHighPriorityWarning(message('robotics:robotslros:devicediag:FolderWritableFailure', username)); return; end % Folder is writable obj.DiagnosticStream.reportInfo(message('robotics:robotslros:devicediag:FolderWritableSuccess').getString); % Check if Catkin workspace is set up correctly. if ~obj.isCatkinWorkspaceValid(catkinWs) if obj.isROSFolderValid(rosFolder) % If we have a valid ROS folder, so we can % initialize the Catkin workspace diag = message('robotics:robotslros:devicediag:CatkinWsNotValid', catkinWs, hostname, sshPort, username, password, rosFolder); else % We cannot initialize the workspace, because we do not % know where ROS is located. diag = message('robotics:robotslros:devicediag:CatkinWsNotValidNoROS', catkinWs); end obj.DiagnosticStream.reportHighPriorityWarning(diag); return; end % All checks passed successfully obj.DiagnosticStream.reportInfo(message('robotics:robotslros:devicediag:CatkinWsValid').getString); end end end