diff --git a/doc/architecture/primary_client.rst b/doc/architecture/primary_client.rst index 57510d265..1fc044f2d 100644 --- a/doc/architecture/primary_client.rst +++ b/doc/architecture/primary_client.rst @@ -33,10 +33,11 @@ Method signature: ); | The ``sendScriptBlocking`` method will also accept valid URScript code, but blocks until the execution result of the given program is available. -| Prior to transferring the program it will first check that the robot is in a state where it can execute programs, if not it returns false. +| Prior to transferring the program it will first check that the robot is in a state where it can execute programs, otherwise an exception is thrown. | If the robot is ready, the program is then transferred, and the method will wait for the robot to report that the program has either started, finished or encountered an error. -| If the program has not started within the given ``timeout``, the method returns false. -| If the robot encounters an error or runtime exception during program execution the method also returns false. -| If ``fail_on_warnings`` is true, it will also return false, if the robot reports a warning during program execution. Note: protective stops are reported as warnings by the robot. -| The method only returns true if the program is successfully executed on the robot. +| If the program has not started within the given ``timeout``, the method throws an exception. +| If the robot encounters an error or runtime exception during program execution the method also throws an exception. +| If ``fail_on_warnings`` is true, it will also throw an exception, if the robot reports a warning during program execution. Note: protective stops are reported as warnings by the robot. +| If no exceptions are thrown, the script has been executed successfully. | This method also accepts secondary programs, but no feedback is available for those, so it will behave similarly to the ``sendScript`` method in those cases, except for the pre-transfer checks. +| The exact exceptions that are thrown in various cases can be seen in the `primary client header file `_. diff --git a/examples/send_script.cpp b/examples/send_script.cpp index a832fcfa1..7b4d02221 100644 --- a/examples/send_script.cpp +++ b/examples/send_script.cpp @@ -45,13 +45,10 @@ def example_fun(): relative_move = p[0,-0.1,0,0,0,0] movel(pose_trans(current_pose, relative_move), t=1) end)"""; - - if (client.sendScriptBlocking(fully_defined_script)) - { - // The function definition can also be omitted - // A function name will then be auto generated - client.sendScriptBlocking(R"(textmsg("Successful program execution"))"); - } + client.sendScriptBlocking(fully_defined_script); + // The function definition can also be omitted + // A function name will then be auto generated + client.sendScriptBlocking(R"(textmsg("Successful program execution"))"); // A script-function name can also be passed to the method // A timeout can also be given to limit the wait for the passed function to start. If timeout = 0, it will // wait indefinitely. @@ -66,18 +63,26 @@ end )"; client.sendScriptBlocking(secondary_script); - // Sending wrong script code will result in a clear error + // Sending wrong script code will result in an exception with a clear explanation const std::string bad_script_code = R"""( def bad_code(): current_pose = get_target_tcp_pose() movel(current_pos) # note pose vs pos end)"""; URCL_LOG_INFO("Sending bad script code..."); - bool success = client.sendScriptBlocking(bad_script_code); + try { - std::stringstream ss; - ss << "Execution of bad code successful? " << std::boolalpha << success; - URCL_LOG_INFO("%s", ss.str().c_str()); + client.sendScriptBlocking(bad_script_code); + } + catch (const RobotRuntimeException& exc) + { + URCL_LOG_INFO("Caught expected runtime exception from sendScriptBlocking"); + URCL_LOG_INFO(exc.what()); + } + catch (const UrException& exc) + { + URCL_LOG_ERROR("Caught unexpected exception from sendScriptBlocking"); + URCL_LOG_ERROR(exc.what()); } // We can also send script code without any checks @@ -87,7 +92,7 @@ end)"""; // E.g. sending the bad script here will not give us any information // The return value will only tell us that the script code has been sent to the robot. URCL_LOG_INFO("Sending bad script code without feedback..."); - success = client.sendScript(bad_script_code); + bool success = client.sendScript(bad_script_code); { std::stringstream ss; ss << "Bad code sent to robot successfully? " << std::boolalpha << success; diff --git a/include/ur_client_library/exceptions.h b/include/ur_client_library/exceptions.h index c3c46e8fc..aa3f3d6c9 100644 --- a/include/ur_client_library/exceptions.h +++ b/include/ur_client_library/exceptions.h @@ -33,7 +33,9 @@ #include #include #include +#include #include "ur/version_information.h" +#include "ur_client_library/ur/datatypes.h" #ifdef _WIN32 # define NOMINMAX @@ -336,5 +338,133 @@ class ScriptCodeSyntaxException : public UrException return std::runtime_error::what(); } }; + +class RobotModeException : public UrException +{ +public: + explicit RobotModeException() = delete; + + explicit RobotModeException(const std::string& operation, const RobotMode& required, const RobotMode& actual) + : std::runtime_error("Incorrect robot mode: " + robotModeString(actual)) + { + std::stringstream ss; + ss << "Robot is in incorrect mode for the requested operation: " << operation << "\n" + << "Required robot mode: " << urcl::robotModeString(required) << " (" << int(required) << ") \n" + << "Actual robot mode: " << urcl::robotModeString(actual) << " (" << int(actual) << ")"; + text_ = ss.str(); + } + + virtual ~RobotModeException() = default; + + virtual const char* what() const noexcept override + { + return text_.c_str(); + } + +private: + std::string text_; +}; + +class SafetyModeException : public UrException +{ +public: + explicit SafetyModeException() = delete; + + explicit SafetyModeException(const std::string& operation, const std::vector& options, + const urcl::SafetyMode& actual) + : std::runtime_error("Incorrect safety mode: " + safetyModeString(actual)) + { + std::stringstream ss; + ss << "Robot is in incorrect safety mode for the requested operation: " << operation << "\n" + << "Safety mode should be one of: \n"; + + for (auto mode : options) + { + ss << urcl::safetyModeString(mode) << " (" << int(mode) << ")\n"; + } + ss << "\n" + << "Actual safety mode: " << urcl::safetyModeString(actual) << " (" << int(actual) << ")"; + text_ = ss.str(); + } + + virtual ~SafetyModeException() = default; + + virtual const char* what() const noexcept override + { + return text_.c_str(); + } + +private: + std::string text_; +}; + +class StreamNotConnectedException : public UrException +{ +public: + explicit StreamNotConnectedException() = delete; + + explicit StreamNotConnectedException(const std::string& text) : std::runtime_error(text) + { + } + + virtual ~StreamNotConnectedException() = default; + + virtual const char* what() const noexcept override + { + return std::runtime_error::what(); + } +}; + +class RobotRuntimeException : public UrException +{ +public: + explicit RobotRuntimeException() = delete; + + explicit RobotRuntimeException(const std::string& text) : std::runtime_error(text) + { + } + + virtual ~RobotRuntimeException() = default; + + virtual const char* what() const noexcept override + { + return std::runtime_error::what(); + } +}; + +class ReadOnlyInterfaceException : public UrException +{ +public: + explicit ReadOnlyInterfaceException() = delete; + + explicit ReadOnlyInterfaceException(const std::string& text) : std::runtime_error(text) + { + } + + virtual ~ReadOnlyInterfaceException() = default; + + virtual const char* what() const noexcept override + { + return std::runtime_error::what(); + } +}; + +class RobotErrorCodeException : public UrException +{ +public: + explicit RobotErrorCodeException() = delete; + + explicit RobotErrorCodeException(const std::string& text) : std::runtime_error(text) + { + } + + virtual ~RobotErrorCodeException() = default; + + virtual const char* what() const noexcept override + { + return std::runtime_error::what(); + } +}; + } // namespace urcl #endif // ifndef UR_CLIENT_LIBRARY_EXCEPTIONS_H_INCLUDED diff --git a/include/ur_client_library/primary/primary_client.h b/include/ur_client_library/primary/primary_client.h index f4f1b110c..c4f57c924 100644 --- a/include/ur_client_library/primary/primary_client.h +++ b/include/ur_client_library/primary/primary_client.h @@ -109,7 +109,7 @@ class PrimaryClient * The given code must be valid according the UR Scripting Manual. The given script code will be automatically wrapped * in a function definition, if it is not already. Secondary programs can also be passed to this function, but must be * fully defined as a secondary program when calling. Secondary programs create no feedback, so this function will - * return true as soon as the program is uploaded successfully to the robot (same as the sendScript function). + * return as soon as the program is uploaded successfully to the robot (same as the sendScript function). * * \param program URScript code that shall be executed by the robot. * @@ -126,10 +126,17 @@ class PrimaryClient * \throw urcl::ScriptCodeSyntaxException if the given script code has syntax errors, which are checked here. * \throw urcl::UrException if the stop command cannot be sent to the robot. * \throw urcl::TimeoutException if the robot doesn't stop the program within the given timeout. - * - * \returns true on successful execution of the script, false otherwise + * \throw urcl::TimeoutException if the robot mode is not received within 1 second. + * \throw urcl::TimeoutException if the program does not start within the given timeout. + * \throw urcl::RobotModeException if the robot is in an incorrect mode for script execution. + * \throw urcl::SafetyModeException if the robot is in an incorrect safety mode for script execution + * \throw urcl::StreamNotConnectedException if the script cannot be transferred to the robot. + * \throw urcl::RobotRuntimeException if the given script causes a runtime exception on the robot. + * \throw urcl::ReadOnlyInterfaceException if the primary interface is in read-only mode when the script is + * transferred. This can happen if the robot was recently switched from manual to remote control mode. + * \throw urcl::RobotErrorCodeException if the robot encounters an error during script execution. */ - bool sendScriptBlocking(const std::string& program, std::string script_name = "", + void sendScriptBlocking(const std::string& program, std::string script_name = "", std::chrono::milliseconds start_timeout = std::chrono::seconds(1), bool fail_on_warnings = true); diff --git a/include/ur_client_library/primary/primary_consumer.h b/include/ur_client_library/primary/primary_consumer.h index a140ceded..a46756627 100644 --- a/include/ur_client_library/primary/primary_consumer.h +++ b/include/ur_client_library/primary/primary_consumer.h @@ -136,23 +136,24 @@ class PrimaryConsumer : public AbstractPrimaryConsumer switch (code.report_level) { - case urcl::primary_interface::ReportLevel::DEBUG: - case urcl::primary_interface::ReportLevel::DEVL_DEBUG: - case urcl::primary_interface::ReportLevel::DEVL_INFO: - case urcl::primary_interface::ReportLevel::DEVL_WARNING: - case urcl::primary_interface::ReportLevel::DEVL_VIOLATION: - case urcl::primary_interface::ReportLevel::DEVL_FAULT: + case ReportLevel::DEBUG: + case ReportLevel::DEVL_DEBUG: + case ReportLevel::DEVL_INFO: + case ReportLevel::DEVL_WARNING: + case ReportLevel::DEVL_VIOLATION: + case ReportLevel::DEVL_FAULT: + case ReportLevel::DEVL_CRITICAL_FAULT: URCL_LOG_DEBUG(log_contents.c_str()); break; - case urcl::primary_interface::ReportLevel::INFO: + case ReportLevel::INFO: URCL_LOG_INFO(log_contents.c_str()); break; - case urcl::primary_interface::ReportLevel::WARNING: + case ReportLevel::WARNING: URCL_LOG_WARN(log_contents.c_str()); break; - default: - // urcl::primary_interface::ReportLevel::VIOLATION: - // urcl::primary_interface::ReportLevel::FAULT: + case ReportLevel::VIOLATION: + case ReportLevel::FAULT: + case ReportLevel::CRITICAL_FAULT: URCL_LOG_ERROR(log_contents.c_str()); break; } diff --git a/include/ur_client_library/primary/robot_message/error_code_message.h b/include/ur_client_library/primary/robot_message/error_code_message.h index a4fc63bb3..9e3a69c8e 100644 --- a/include/ur_client_library/primary/robot_message/error_code_message.h +++ b/include/ur_client_library/primary/robot_message/error_code_message.h @@ -29,24 +29,12 @@ #define UR_CLIENT_LIBRARY_PRIMARY_ERROR_CODE_MESSAGE_H_INCLUDED #include "ur_client_library/primary/robot_message.h" +#include "ur_client_library/ur/datatypes.h" namespace urcl { namespace primary_interface { -enum class ReportLevel : int32_t -{ - DEBUG = 0, - INFO = 1, - WARNING = 2, - VIOLATION = 3, - FAULT = 4, - DEVL_DEBUG = 128, - DEVL_INFO = 129, - DEVL_WARNING = 130, - DEVL_VIOLATION = 131, - DEVL_FAULT = 132 -}; struct ErrorCode { diff --git a/include/ur_client_library/ur/datatypes.h b/include/ur_client_library/ur/datatypes.h index 3af4ebb7f..701a02c7a 100644 --- a/include/ur_client_library/ur/datatypes.h +++ b/include/ur_client_library/ur/datatypes.h @@ -120,6 +120,54 @@ enum class RobotSeries UR_SERIES = 3 }; +enum class ReportLevel : int32_t +{ + DEBUG = 0, + INFO = 1, + WARNING = 2, + VIOLATION = 3, + FAULT = 4, + CRITICAL_FAULT = 5, + DEVL_DEBUG = 128, + DEVL_INFO = 129, + DEVL_WARNING = 130, + DEVL_VIOLATION = 131, + DEVL_FAULT = 132, + DEVL_CRITICAL_FAULT = 133 +}; + +inline std::string reportLevelString(const ReportLevel& code) +{ + switch (code) + { + case ReportLevel::DEBUG: + return "DEBUG"; + case ReportLevel::INFO: + return "INFO"; + case ReportLevel::WARNING: + return "WARNING"; + case ReportLevel::VIOLATION: + return "VIOLATION"; + case ReportLevel::FAULT: + return "FAULT"; + case ReportLevel::CRITICAL_FAULT: + return "CRITICAL_FAULT"; + case ReportLevel::DEVL_DEBUG: + return "DEVL_DEBUG"; + case ReportLevel::DEVL_INFO: + return "DEVL_INFO"; + case ReportLevel::DEVL_WARNING: + return "DEVL_WARNING"; + case ReportLevel::DEVL_VIOLATION: + return "DEVL_VIOLATION"; + case ReportLevel::DEVL_FAULT: + return "DEVL_FAULT"; + case ReportLevel::DEVL_CRITICAL_FAULT: + return "DEVL_CRITICAL_FAULT"; + } + throw std::invalid_argument("Unknown report level: " + std::to_string(static_cast(code))); +} + inline std::string robotModeString(const RobotMode& mode) { switch (mode) diff --git a/src/primary/primary_client.cpp b/src/primary/primary_client.cpp index a413c648a..c53af8540 100644 --- a/src/primary/primary_client.cpp +++ b/src/primary/primary_client.cpp @@ -137,7 +137,7 @@ bool PrimaryClient::safetyModeAllowsExecution() } } -bool PrimaryClient::sendScriptBlocking(const std::string& program, std::string script_name, +void PrimaryClient::sendScriptBlocking(const std::string& program, std::string script_name, std::chrono::milliseconds timeout, bool fail_on_warnings) { ScriptInfo script_info = prepare_script(program, script_name); @@ -150,8 +150,7 @@ bool PrimaryClient::sendScriptBlocking(const std::string& program, std::string s auto now = std::chrono::system_clock::now(); if (std::chrono::duration_cast(now - start).count() > robot_mode_timeout.count()) { - URCL_LOG_ERROR("Robot mode not received within %lld ms, exiting.", robot_mode_timeout.count()); - return false; + throw TimeoutException("Robot mode not received within timeout. ", robot_mode_timeout); } URCL_LOG_INFO("Robot mode not received yet, waiting for it to be received."); std::chrono::milliseconds update_period(100); @@ -161,20 +160,17 @@ bool PrimaryClient::sendScriptBlocking(const std::string& program, std::string s if (robot_mode != RobotMode::RUNNING) { - URCL_LOG_ERROR("Robot is not running, cannot execute script."); - std::stringstream ss; - ss << "Robot is in mode: " << urcl::robotModeString(robot_mode) << " (" << int(robot_mode) << ")"; - URCL_LOG_ERROR(ss.str().c_str()); - return false; + throw RobotModeException("Script execution via primary interface", urcl::RobotMode::RUNNING, robot_mode); } if (!safetyModeAllowsExecution()) { - URCL_LOG_ERROR("Robot safety mode does not allow for script execution, cannot execute script."); - std::stringstream ss; - ss << "Robot safety mode is: " << safetyModeString(getSafetyMode()) << " (" << unsigned(getSafetyMode()) << ")"; - URCL_LOG_ERROR(ss.str().c_str()); - return false; + std::vector allowed_modes = { urcl::SafetyMode::NORMAL, urcl::SafetyMode::REDUCED, + urcl::SafetyMode::RECOVERY }; + allowed_modes.push_back(urcl::SafetyMode::UNDEFINED_SAFETY_MODE); // Remove when safety mode gets updated + // continuously + + throw SafetyModeException("Script execution via primary interface", allowed_modes, getSafetyMode()); } // Clear runtime exception { @@ -192,13 +188,16 @@ bool PrimaryClient::sendScriptBlocking(const std::string& program, std::string s bool script_sent = sendScript(script_info.script_code); if (!script_sent) { - URCL_LOG_ERROR("Script could not be sent."); - return false; + throw StreamNotConnectedException("Script could not be sent to the robot. Ensure that the primary interface is " + "connected."); } // No feedback from secondary programs, so we assume success if (script_info.script_type == ScriptTypes::SEC) { - return true; + URCL_LOG_INFO("Script %s was determined to be a secondary program. Script was transferred successfully, but no " + "further feedback will be provided.", + script_info.script_name.c_str()); + return; } const auto script_start_time = std::chrono::system_clock::now(); @@ -219,10 +218,10 @@ bool PrimaryClient::sendScriptBlocking(const std::string& program, std::string s std::scoped_lock lock(runtime_exception_mutex_); if (latest_runtime_exception_ != nullptr) { - URCL_LOG_ERROR("Runtime exception occured during script execution. Runtime exception type: %s", - latest_runtime_exception_->text_.c_str()); std::stringstream ss; - ss << "Exception occured at line " << latest_runtime_exception_->line_number_ << ", column " + ss << "Runtime exception occured during script execution." + << "Runtime exception type: " << latest_runtime_exception_->text_ << "\n" + << "Exception occured at line " << latest_runtime_exception_->line_number_ << ", column " << latest_runtime_exception_->column_number_ << "\n"; // Line and column numbers should always be 1-based, but we check that they are greater // than 0 just to be sure before using them for indexing in the debug print below @@ -249,9 +248,8 @@ bool PrimaryClient::sendScriptBlocking(const std::string& program, std::string s ss << "^<--- here\n"; } } - URCL_LOG_ERROR(ss.str().c_str()); } - return false; + throw RobotRuntimeException(ss.str()); } } @@ -261,21 +259,38 @@ bool PrimaryClient::sendScriptBlocking(const std::string& program, std::string s bool is_error = false; bool is_warning = false; bool is_read_only = false; + + std::stringstream error_stream; + error_stream << "Robot error codes received during script execution: \n"; + for (auto error : errors) { - if (error.report_level == ReportLevel::VIOLATION || error.report_level == ReportLevel::FAULT) - { - URCL_LOG_ERROR("Robot error code with severity VIOLATION or FAULT received during script execution. Robot " - "error code: %s", - error.to_string.c_str()); - is_error = true; - } - if (error.report_level == ReportLevel::WARNING) + switch (error.report_level) { - URCL_LOG_WARN("Robot error code with severity WARNING received during script execution. Robot " - "error code: %s", - error.to_string.c_str()); - is_warning = true; + case ReportLevel::VIOLATION: + case ReportLevel::FAULT: + case ReportLevel::CRITICAL_FAULT: + error_stream << "Code: " << error.to_string << ", severity: " << reportLevelString(error.report_level) + << "\n"; + is_error = true; + break; + case ReportLevel::WARNING: + if (fail_on_warnings) + { + error_stream << "Code: " << error.to_string << ", severity: " << reportLevelString(error.report_level) + << "\n"; + } + is_warning = true; + break; + case ReportLevel::DEBUG: + case ReportLevel::INFO: + case ReportLevel::DEVL_DEBUG: + case ReportLevel::DEVL_INFO: + case ReportLevel::DEVL_WARNING: + case ReportLevel::DEVL_VIOLATION: + case ReportLevel::DEVL_FAULT: + case ReportLevel::DEVL_CRITICAL_FAULT: + break; } if (error.message_code == 210) { @@ -294,16 +309,17 @@ bool PrimaryClient::sendScriptBlocking(const std::string& program, std::string s } else { - URCL_LOG_ERROR("Script cannot be executed since primary client is connected to a read-only primary " - "interface. If you have switched from local to remote mode recently, try reconnecting the " - "primary client and send the script code again."); + throw ReadOnlyInterfaceException("Script cannot be executed since primary client is connected to a read-only " + "primary " + "interface. If you have switched from local to remote mode recently, try " + "reconnecting the " + "primary client and send the script code again."); } - URCL_LOG_ERROR("Script execution failed due to error code(s) received from robot."); - return false; + throw RobotErrorCodeException(error_stream.str()); } if (is_warning && fail_on_warnings) { - return false; + throw RobotErrorCodeException(error_stream.str()); } } @@ -354,7 +370,7 @@ bool PrimaryClient::sendScriptBlocking(const std::string& program, std::string s if (now - program_stopped_time >= post_stop_drain_period) { URCL_LOG_INFO("Script with name %s executed successfully", script_info.script_name.c_str()); - return true; + return; } } else @@ -364,8 +380,8 @@ bool PrimaryClient::sendScriptBlocking(const std::string& program, std::string s if (!script_started && elapsed_time > timeout) { - URCL_LOG_ERROR("Script %s not started within timeout", script_info.script_name.c_str()); - return false; + throw urcl::TimeoutException("Script with name " + script_info.script_name + " not started within timeout. ", + timeout); } } std::chrono::milliseconds wait_period(10); @@ -420,7 +436,7 @@ ScriptInfo PrimaryClient::prepare_script(std::string script, std::string script_ if (stripped_script.size() == 0) { - throw urcl::ScriptCodeSyntaxException("Script is empty after stripping comments and whitespace."); + throw ScriptCodeSyntaxException("Script is empty after stripping comments and whitespace."); } // Use given script name or create one @@ -478,16 +494,16 @@ ScriptInfo PrimaryClient::prepare_script(std::string script, std::string script_ static const std::regex valid_name(R"(^[A-Za-z_][A-Za-z0-9_]*$)"); if (!std::regex_match(actual_script_name, valid_name)) { - throw urcl::ScriptCodeSyntaxException("Invalid script name: '" + actual_script_name + - "'. Can only contain letters, numbers and underscores. First character " - "must be a letter or underscore."); + throw ScriptCodeSyntaxException("Invalid script name: '" + actual_script_name + + "'. Can only contain letters, numbers and underscores. First character " + "must be a letter or underscore."); } if (stripped_script.back().substr(0, 3).find("end") == script.npos) { - throw urcl::ScriptCodeSyntaxException("Script contains either function definition or secondary process " - "definition, " - "but no 'end' term. Script is invalid."); + throw ScriptCodeSyntaxException("Script contains either function definition or secondary process " + "definition, " + "but no 'end' term. Script is invalid."); } // Concatenate all the script lines in to the final script diff --git a/tests/fake_primary_server.cpp b/tests/fake_primary_server.cpp index 76c8981ea..4edd903d3 100644 --- a/tests/fake_primary_server.cpp +++ b/tests/fake_primary_server.cpp @@ -244,9 +244,8 @@ bool FakePrimaryServer::sendSafetyModeMessage(SafetyMode safety_mode, int32_t me return sendRobotMessage(primary_interface::RobotMessagePackageType::ROBOT_MESSAGE_SAFETY_MODE, payload); } -bool FakePrimaryServer::sendErrorCodeMessage(int32_t message_code, int32_t message_argument, - primary_interface::ReportLevel report_level, const std::string& text, - uint32_t data_type, uint32_t data) +bool FakePrimaryServer::sendErrorCodeMessage(int32_t message_code, int32_t message_argument, ReportLevel report_level, + const std::string& text, uint32_t data_type, uint32_t data) { std::vector payload(sizeof(int32_t) + sizeof(int32_t) + sizeof(int32_t) + sizeof(uint32_t) + sizeof(uint32_t) + text.size()); diff --git a/tests/fake_primary_server.h b/tests/fake_primary_server.h index e4425bc57..5071718e2 100644 --- a/tests/fake_primary_server.h +++ b/tests/fake_primary_server.h @@ -187,7 +187,7 @@ class FakePrimaryServer /*! * \brief Send an ErrorCodeMessage. */ - bool sendErrorCodeMessage(int32_t message_code, int32_t message_argument, primary_interface::ReportLevel report_level, + bool sendErrorCodeMessage(int32_t message_code, int32_t message_argument, ReportLevel report_level, const std::string& text, uint32_t data_type = 0, uint32_t data = 0); // ===================== Concrete RobotState helpers ===================== diff --git a/tests/test_fake_primary_server.cpp b/tests/test_fake_primary_server.cpp index 658880289..642f69faa 100644 --- a/tests/test_fake_primary_server.cpp +++ b/tests/test_fake_primary_server.cpp @@ -233,11 +233,11 @@ TEST_F(FakePrimaryServerTest, send_safety_mode_message_roundtrip) TEST_F(FakePrimaryServerTest, send_error_code_message_roundtrip) { - ASSERT_TRUE(server_->sendErrorCodeMessage(210, 0, primary_interface::ReportLevel::VIOLATION, "read-only PI")); + ASSERT_TRUE(server_->sendErrorCodeMessage(210, 0, ReportLevel::VIOLATION, "read-only PI")); auto msg = client_->consumer().waitFor(); ASSERT_NE(msg, nullptr); EXPECT_EQ(msg->message_code_, 210); - EXPECT_EQ(msg->report_level_, primary_interface::ReportLevel::VIOLATION); + EXPECT_EQ(msg->report_level_, ReportLevel::VIOLATION); EXPECT_EQ(msg->text_, "read-only PI"); } diff --git a/tests/test_primary_client.cpp b/tests/test_primary_client.cpp index bd4beca44..2fe840c72 100644 --- a/tests/test_primary_client.cpp +++ b/tests/test_primary_client.cpp @@ -486,26 +486,26 @@ TEST_F(PrimaryClientTest, test_send_script_blocking_happy_path) " sleep(0.1)\n" " sync()\n" "end"; - EXPECT_TRUE(client_->sendScriptBlocking(fully_defined_script)); + EXPECT_NO_THROW(client_->sendScriptBlocking(fully_defined_script)); const std::string part_defined_script = "textmsg(\"still running\")\n" "sleep(0.1)\n" "sync()\n"; - EXPECT_TRUE(client_->sendScriptBlocking(part_defined_script)); - EXPECT_TRUE(client_->sendScriptBlocking(part_defined_script, "test_def")); + EXPECT_NO_THROW(client_->sendScriptBlocking(part_defined_script)); + EXPECT_NO_THROW(client_->sendScriptBlocking(part_defined_script, "test_def")); std::string sec_script = "sec test_sec():\n textmsg(\"Still running\")\nend"; - EXPECT_TRUE(client_->sendScriptBlocking(sec_script, "test_sec")); + EXPECT_NO_THROW(client_->sendScriptBlocking(sec_script, "test_sec")); } TEST_F(PrimaryClientTest, test_send_script_blocking_fails_on_nonrunning_robot) { EXPECT_NO_THROW(client_->start()); EXPECT_NO_THROW(client_->commandPowerOff()); - EXPECT_FALSE(client_->sendScriptBlocking("textmsg(\"Still running\")")); + EXPECT_THROW(client_->sendScriptBlocking("textmsg(\"Still running\")"), RobotModeException); EXPECT_NO_THROW(client_->commandPowerOn()); - EXPECT_FALSE(client_->sendScriptBlocking("textmsg(\"Still running\")")); + EXPECT_THROW(client_->sendScriptBlocking("textmsg(\"Still running\")"), RobotModeException); EXPECT_NO_THROW(client_->commandBrakeRelease()); - EXPECT_TRUE(client_->sendScriptBlocking("textmsg(\"Still running\")")); + EXPECT_NO_THROW(client_->sendScriptBlocking("textmsg(\"Still running\")")); } TEST_F(PrimaryClientTest, test_send_script_blocking_fails_on_bad_safety_mode) @@ -515,10 +515,10 @@ TEST_F(PrimaryClientTest, test_send_script_blocking_fails_on_bad_safety_mode) EXPECT_NO_THROW(client_->commandBrakeRelease()); ASSERT_TRUE(client_->safetyModeAllowsExecution()); - EXPECT_FALSE(client_->sendScriptBlocking("protective_stop()")); - EXPECT_FALSE(client_->sendScriptBlocking("textmsg(\"Still running\")")); + EXPECT_THROW(client_->sendScriptBlocking("protective_stop()"), RobotErrorCodeException); + EXPECT_THROW(client_->sendScriptBlocking("textmsg(\"Still running\")"), SafetyModeException); EXPECT_NO_THROW(client_->commandUnlockProtectiveStop()); - EXPECT_TRUE(client_->sendScriptBlocking("textmsg(\"Still running\")")); + EXPECT_NO_THROW(client_->sendScriptBlocking("textmsg(\"Still running\")")); } TEST_F(PrimaryClientTest, test_send_script_blocking_throw_on_malformed_scripts) @@ -546,7 +546,7 @@ TEST_F(PrimaryClientTest, test_send_script_blocking_fail_on_runtime_exception) EXPECT_NO_THROW(client_->commandPowerOff()); EXPECT_NO_THROW(client_->commandBrakeRelease()); // Non-invertible goal, should throw runtime exception - EXPECT_FALSE(client_->sendScriptBlocking("movej(p[10,0,0,0,0,0])")); + EXPECT_THROW(client_->sendScriptBlocking("movej(p[10,0,0,0,0,0])"), RobotRuntimeException); } TEST_F(PrimaryClientTest, test_send_script_blocking_fail_on_robot_errors) @@ -555,10 +555,10 @@ TEST_F(PrimaryClientTest, test_send_script_blocking_fail_on_robot_errors) EXPECT_NO_THROW(client_->commandPowerOff()); EXPECT_NO_THROW(client_->commandBrakeRelease()); // Impossible movement, will trigger a warning and protective stop - EXPECT_FALSE(client_->sendScriptBlocking("movel(p[10,0,0,0,0,0])")); + EXPECT_THROW(client_->sendScriptBlocking("movel(p[10,0,0,0,0,0])"), RobotErrorCodeException); // reset the robot ASSERT_NO_THROW(client_->commandUnlockProtectiveStop()); - EXPECT_TRUE(client_->sendScriptBlocking("movej([0.5,-0.5,0.5,0,0,0])")); + EXPECT_NO_THROW(client_->sendScriptBlocking("movej([0.5,-0.5,0.5,0,0,0])")); } TEST_F(PrimaryClientTest, test_send_script_blocking_fail_on_bad_script) @@ -567,13 +567,13 @@ TEST_F(PrimaryClientTest, test_send_script_blocking_fail_on_bad_script) EXPECT_NO_THROW(client_->commandPowerOff()); EXPECT_NO_THROW(client_->commandBrakeRelease()); - EXPECT_FALSE(client_->sendScriptBlocking("non_existing_func()")); + EXPECT_THROW(client_->sendScriptBlocking("non_existing_func()"), RobotRuntimeException); const std::string script_code = "def illegal_fun():\n" " calldoesntexist()\n" "end"; - EXPECT_FALSE(client_->sendScriptBlocking(script_code)); + EXPECT_THROW(client_->sendScriptBlocking(script_code), RobotRuntimeException); } TEST_F(PrimaryClientTest, test_send_script_blocking_ignore_warnings) @@ -582,7 +582,7 @@ TEST_F(PrimaryClientTest, test_send_script_blocking_ignore_warnings) EXPECT_NO_THROW(client_->commandPowerOff()); EXPECT_NO_THROW(client_->commandBrakeRelease()); // Trigger protective stop (warning level error code) - EXPECT_TRUE(client_->sendScriptBlocking("protective_stop()", "", std::chrono::milliseconds(1000), false)); + EXPECT_NO_THROW(client_->sendScriptBlocking("protective_stop()", "", std::chrono::milliseconds(1000), false)); // reset the robot ASSERT_NO_THROW(client_->commandUnlockProtectiveStop()); } @@ -593,22 +593,23 @@ TEST_F(PrimaryClientTest, test_send_script_blocking_replace_long_names) EXPECT_NO_THROW(client_->commandPowerOff()); EXPECT_NO_THROW(client_->commandBrakeRelease()); const std::string name = "this_is_a_very_long_script_name_that_should_be_truncated"; - EXPECT_TRUE(client_->sendScriptBlocking("textmsg(\"Still running\")", name)); + EXPECT_NO_THROW(client_->sendScriptBlocking("textmsg(\"Still running\")", name)); const std::string long_name_script = "def " + name + "():\n" " textmsg(\"still running\")\n" " sleep(0.1)\n" " sync()\n" "end"; - EXPECT_TRUE(client_->sendScriptBlocking(long_name_script)); + EXPECT_NO_THROW(client_->sendScriptBlocking(long_name_script)); } TEST_F(PrimaryClientFakeTest, test_send_script_blocking_fail_on_missing_robot_mode) { // We did NOT send a robot mode, yet. - EXPECT_FALSE( - client_->sendScriptBlocking("textmsg(\"Still running\")", "test_fun", std::chrono::milliseconds(1000), false)); + EXPECT_THROW( + client_->sendScriptBlocking("textmsg(\"Still running\")", "test_fun", std::chrono::milliseconds(1000), false), + TimeoutException); server_->setScriptCallback([this]([[maybe_unused]] const std::string& payload) { server_->sendKeyMessage("PROGRAM_XXX_STARTED", "test_fun"); @@ -621,7 +622,7 @@ TEST_F(PrimaryClientFakeTest, test_send_script_blocking_fail_on_missing_robot_mo // std::this_thread::sleep_for(std::chrono::milliseconds(200)); server_->sendRobotModeData(RobotMode::RUNNING, true, true, true, false, false, false, false); }); - EXPECT_TRUE( + EXPECT_NO_THROW( client_->sendScriptBlocking("textmsg(\"Still running\")", "test_fun", std::chrono::milliseconds(1000), false)); if (delayed_robot_mode_thread.joinable()) @@ -642,7 +643,7 @@ TEST_F(PrimaryClientFakeTest, test_send_script_blocking_fail_on_fault) { server_->sendKeyMessage("PROGRAM_XXX_STARTED", "test_fun"); ASSERT_EQ(payload, "def test_fun():\n " + script_code + "\nend\n\n"); - ASSERT_TRUE(server_->sendErrorCodeMessage(999, 0, primary_interface::ReportLevel::FAULT, "Simulated fault")); + ASSERT_TRUE(server_->sendErrorCodeMessage(999, 0, ReportLevel::FAULT, "Simulated fault")); } else { @@ -652,7 +653,8 @@ TEST_F(PrimaryClientFakeTest, test_send_script_blocking_fail_on_fault) }); URCL_LOG_INFO("Sending script that will trigger a fault on the fake server"); - EXPECT_FALSE(client_->sendScriptBlocking(script_code, "test_fun", std::chrono::milliseconds(1000), false)); + EXPECT_THROW(client_->sendScriptBlocking(script_code, "test_fun", std::chrono::milliseconds(1000), false), + RobotErrorCodeException); } TEST_F(PrimaryClientFakeTest, test_send_script_to_read_only_server) @@ -664,11 +666,12 @@ TEST_F(PrimaryClientFakeTest, test_send_script_to_read_only_server) // Make the fake server send an error code message with code 210 (read-only primary interface) when it receives a // script server_->setScriptCallback([this, script_code]([[maybe_unused]] const std::string& payload) { - ASSERT_TRUE(server_->sendErrorCodeMessage(210, 0, primary_interface::ReportLevel::VIOLATION, - "Simulated read-only primary interface error")); + ASSERT_TRUE( + server_->sendErrorCodeMessage(210, 0, ReportLevel::VIOLATION, "Simulated read-only primary interface error")); }); - EXPECT_FALSE(client_->sendScriptBlocking(script_code, "test_fun", std::chrono::milliseconds(1000), false)); + EXPECT_THROW(client_->sendScriptBlocking(script_code, "test_fun", std::chrono::milliseconds(1000), false), + ReadOnlyInterfaceException); } TEST_F(PrimaryClientFakeTest, test_send_script_blocking_timeout_on_no_response) @@ -679,7 +682,48 @@ TEST_F(PrimaryClientFakeTest, test_send_script_blocking_timeout_on_no_response) // We do not set a script callback on the fake server, so it will not respond to the script being sent. This should // cause sendScriptBlocking to time out and return false. - EXPECT_FALSE(client_->sendScriptBlocking(script_code, "test_fun", std::chrono::milliseconds(100), false)); + EXPECT_THROW(client_->sendScriptBlocking(script_code, "test_fun", std::chrono::milliseconds(100), false), + TimeoutException); +} + +TEST_F(PrimaryClientFakeTest, test_error_code_report_levels_get_handled_correctly) +{ + ASSERT_TRUE(server_->sendErrorCodeMessage(999, 0, ReportLevel::DEBUG, "Simulated Debug")); + ASSERT_TRUE(server_->sendErrorCodeMessage(999, 0, ReportLevel::INFO, "Simulated Info")); + ASSERT_TRUE(server_->sendErrorCodeMessage(999, 0, ReportLevel::WARNING, "Simulated Warning")); + ASSERT_TRUE(server_->sendErrorCodeMessage(999, 0, ReportLevel::VIOLATION, "Simulated Violation")); + ASSERT_TRUE(server_->sendErrorCodeMessage(999, 0, ReportLevel::FAULT, "Simulated Fault")); + ASSERT_TRUE(server_->sendErrorCodeMessage(999, 0, ReportLevel::CRITICAL_FAULT, "Simulated Critical Fault")); + ASSERT_TRUE(server_->sendErrorCodeMessage(999, 0, ReportLevel::DEVL_DEBUG, "Simulated DEVL Debug")); + ASSERT_TRUE(server_->sendErrorCodeMessage(999, 0, ReportLevel::DEVL_INFO, "Simulated DEVL Info")); + ASSERT_TRUE(server_->sendErrorCodeMessage(999, 0, ReportLevel::DEVL_WARNING, "Simulated DEVL Warning")); + ASSERT_TRUE(server_->sendErrorCodeMessage(999, 0, ReportLevel::DEVL_VIOLATION, "Simulated DEVL Violation")); + ASSERT_TRUE(server_->sendErrorCodeMessage(999, 0, ReportLevel::DEVL_FAULT, "Simulated DEVL Fault")); + ASSERT_TRUE(server_->sendErrorCodeMessage(999, 0, ReportLevel::DEVL_CRITICAL_FAULT, "Simulated DEVL Critical Fault")); + + auto errors = client_->getErrorCodes(); + + // Wait until we asynchronously receive all the error codes we sent. + waitFor( + [&errors, this]() { + auto new_errors = client_->getErrorCodes(); + errors.insert(errors.end(), new_errors.begin(), new_errors.end()); + return errors.size() >= 12; + }, + std::chrono::seconds(1)); + + EXPECT_EQ(errors[0].report_level, ReportLevel::DEBUG); + EXPECT_EQ(errors[1].report_level, ReportLevel::INFO); + EXPECT_EQ(errors[2].report_level, ReportLevel::WARNING); + EXPECT_EQ(errors[3].report_level, ReportLevel::VIOLATION); + EXPECT_EQ(errors[4].report_level, ReportLevel::FAULT); + EXPECT_EQ(errors[5].report_level, ReportLevel::CRITICAL_FAULT); + EXPECT_EQ(errors[6].report_level, ReportLevel::DEVL_DEBUG); + EXPECT_EQ(errors[7].report_level, ReportLevel::DEVL_INFO); + EXPECT_EQ(errors[8].report_level, ReportLevel::DEVL_WARNING); + EXPECT_EQ(errors[9].report_level, ReportLevel::DEVL_VIOLATION); + EXPECT_EQ(errors[10].report_level, ReportLevel::DEVL_FAULT); + EXPECT_EQ(errors[11].report_level, ReportLevel::DEVL_CRITICAL_FAULT); } int main(int argc, char* argv[]) diff --git a/tests/test_primary_parser.cpp b/tests/test_primary_parser.cpp index 114612a32..ac2549944 100644 --- a/tests/test_primary_parser.cpp +++ b/tests/test_primary_parser.cpp @@ -341,6 +341,33 @@ const unsigned char SAFETY_MODE_MESSAGE_STRING[] = { 0x80, 0x07, 0x06, 0x05 }; +const unsigned char REPORT_LEVELS_TEMPLATE[] = { + // message size + 0x00, 0x00, 0x00, 0x20, + // message type robot message + 0x14, + // timestamp + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // source + 0xfd, + // robot_message_type ERROR_CODE + 0x06, + // Robot message code + 0x00, 0x00, 0x00, 0x00, + // Robot message argument + 0x00, 0x00, 0x00, 0x00, + // Robot message report level + 0x00, 0x00, 0x00, 0x00, + // data type + 0x00, 0x00, 0x00, 0x00, + // data + 0x80, 0x07, 0x06, 0x05, + // text length + 0x00, 0x01, + // message + 0x00 +}; + class PrimaryParserTest : public ::testing::Test { protected: @@ -826,6 +853,32 @@ TEST_F(PrimaryParserTest, parse_masterboard_data_with_immi) EXPECT_NE(msg_str.find("IMMI current: 0.75"), std::string::npos); } +TEST_F(PrimaryParserTest, parse_error_code_report_levels) +{ + std::vector levels = { 0, 1, 2, 3, 4, 5, 128, 129, 130, 131, 132, 133 }; + std::vector level_strings = { "DEBUG", "INFO", "WARNING", "VIOLATION", + "FAULT", "CRITICAL_FAULT", "DEVL_DEBUG", "DEVL_INFO", + "DEVL_WARNING", "DEVL_VIOLATION", "DEVL_FAULT", "DEVL_CRITICAL_FAULT" }; + + unsigned char raw_data[sizeof(REPORT_LEVELS_TEMPLATE)]; + for (size_t i = 0; i < levels.size(); i++) + { + memcpy(raw_data, REPORT_LEVELS_TEMPLATE, sizeof(REPORT_LEVELS_TEMPLATE)); + raw_data[26] = levels[i] & 0xFF; + comm::BinParser bp(raw_data, sizeof(raw_data)); + std::vector> products; + ASSERT_TRUE(parser_.parse(bp, products)); + ASSERT_EQ(products.size(), 1); + + if (primary_interface::ErrorCodeMessage* data = + dynamic_cast(products[0].get())) + { + EXPECT_EQ(data->report_level_, ReportLevel(levels[i])); + EXPECT_EQ(reportLevelString(data->report_level_), level_strings[i]); + } + } +} + int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv);