Skip to content

Commit ef3cac1

Browse files
authored
Add parsing of safety mode messages to the primary interface (UniversalRobots#480)
1 parent beabfc9 commit ef3cac1

12 files changed

Lines changed: 524 additions & 2 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ add_library(urcl
3333
src/primary/robot_message/runtime_exception_message.cpp
3434
src/primary/robot_message/text_message.cpp
3535
src/primary/robot_message/version_message.cpp
36+
src/primary/robot_message/safety_mode_message.cpp
3637
src/primary/robot_state.cpp
3738
src/primary/robot_state/kinematics_info.cpp
3839
src/primary/robot_state/robot_mode_data.cpp

include/ur_client_library/comm/bin_parser.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "ur_client_library/log.h"
3333
#include "ur_client_library/types.h"
3434
#include "ur_client_library/exceptions.h"
35+
#include <variant>
3536

3637
namespace urcl
3738
{
@@ -142,6 +143,20 @@ class BinParser
142143
buf_pos_ += sizeof(T);
143144
}
144145

146+
/*!
147+
* \brief Parses the next bytes as the given type, the given type must match one of the types in the variant
148+
*
149+
* @tparam T Type to parse as
150+
* \param val Reference to variant to write the parsed value to
151+
*/
152+
template <typename T>
153+
void parse(std::variant<uint32_t, int32_t, float>& val)
154+
{
155+
T return_val;
156+
parse<T>(return_val);
157+
val = return_val;
158+
}
159+
145160
/*!
146161
* \brief Parses the next bytes as a double.
147162
*

include/ur_client_library/primary/abstract_primary_consumer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "ur_client_library/primary/robot_state/kinematics_info.h"
3737
#include "ur_client_library/primary/robot_state/robot_mode_data.h"
3838
#include "ur_client_library/primary/robot_state/configuration_data.h"
39+
#include "ur_client_library/primary/robot_message/safety_mode_message.h"
3940

4041
namespace urcl
4142
{
@@ -79,6 +80,7 @@ class AbstractPrimaryConsumer : public comm::IConsumer<PrimaryPackage>
7980
virtual bool consume(ErrorCodeMessage& pkg) = 0;
8081
virtual bool consume(RobotModeData& pkg) = 0;
8182
virtual bool consume(ConfigurationData& pkg) = 0;
83+
virtual bool consume(SafetyModeMessage& pkg) = 0;
8284

8385
private:
8486
/* data */

include/ur_client_library/primary/primary_client.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,34 @@ class PrimaryClient
200200
return consumer_->getRobotModeData();
201201
}
202202

203+
/*!
204+
* \brief Get the latest safety mode message.
205+
*
206+
* The safety mode will be updated in the background. This will always show the latest received
207+
* state independent of the time that has passed since receiving it. The return value of this
208+
* will be a nullptr if no data has been received yet.
209+
*/
210+
std::shared_ptr<SafetyModeMessage> getSafetyModeMessage()
211+
{
212+
return consumer_->getSafetyModeMessage();
213+
}
214+
215+
/*!
216+
* \brief Get the latest safety mode.
217+
*
218+
* The safety mode will be updated in the background. This will always show the latest received
219+
* robot mode independent of the time that has passed since receiving it.
220+
*/
221+
SafetyMode getSafetyMode()
222+
{
223+
auto safety_mode = consumer_->getSafetyModeMessage();
224+
if (safety_mode == nullptr)
225+
{
226+
return SafetyMode::UNDEFINED_SAFETY_MODE;
227+
}
228+
return safety_mode->safety_mode_type_;
229+
}
230+
203231
/*!
204232
* \brief Query if the robot is protective stopped.
205233
*

include/ur_client_library/primary/primary_consumer.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,14 @@ class PrimaryConsumer : public AbstractPrimaryConsumer
170170
return true;
171171
}
172172

173+
virtual bool consume(SafetyModeMessage& pkg) override
174+
{
175+
URCL_LOG_DEBUG("Robot safety mode is now %s", safetyModeString(pkg.safety_mode_type_).c_str());
176+
std::scoped_lock lock(safety_mode_mutex_);
177+
safety_mode_ = std::make_shared<SafetyModeMessage>(pkg);
178+
return true;
179+
}
180+
173181
virtual bool consume(ConfigurationData& pkg) override
174182
{
175183
std::scoped_lock lock(configuration_data_mutex_);
@@ -210,6 +218,18 @@ class PrimaryConsumer : public AbstractPrimaryConsumer
210218
return robot_mode_;
211219
}
212220

221+
/*!
222+
* \brief Get the latest safety mode message.
223+
*
224+
* The safety mode will be updated in the background. This will always show the latest received
225+
* safety mode independent of the time that has passed since receiving it.
226+
*/
227+
std::shared_ptr<SafetyModeMessage> getSafetyModeMessage()
228+
{
229+
std::scoped_lock lock(safety_mode_mutex_);
230+
return safety_mode_;
231+
}
232+
213233
/*!
214234
* \brief Get the latest version information.
215235
*
@@ -245,6 +265,8 @@ class PrimaryConsumer : public AbstractPrimaryConsumer
245265
std::shared_ptr<VersionInformation> version_information_;
246266
std::shared_ptr<ConfigurationData> configuration_data_;
247267
std::mutex configuration_data_mutex_;
268+
std::mutex safety_mode_mutex_;
269+
std::shared_ptr<SafetyModeMessage> safety_mode_;
248270
};
249271

250272
} // namespace primary_interface

include/ur_client_library/primary/primary_parser.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "ur_client_library/primary/robot_message/error_code_message.h"
3535
#include "ur_client_library/primary/robot_state/robot_mode_data.h"
3636
#include "ur_client_library/primary/robot_state/configuration_data.h"
37+
#include "ur_client_library/primary/robot_message/safety_mode_message.h"
3738

3839
namespace urcl
3940
{
@@ -241,6 +242,8 @@ class PrimaryParser : public comm::Parser<PrimaryPackage>
241242
return new KeyMessage(timestamp, source);
242243
case RobotMessagePackageType::ROBOT_MESSAGE_RUNTIME_EXCEPTION:
243244
return new RuntimeExceptionMessage(timestamp, source);
245+
case RobotMessagePackageType::ROBOT_MESSAGE_SAFETY_MODE:
246+
return new SafetyModeMessage(timestamp, source);
244247
default:
245248
return new RobotMessage(timestamp, source, type);
246249
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// -- BEGIN LICENSE BLOCK ----------------------------------------------
2+
// Copyright 2026 Universal Robots A/S
3+
//
4+
// Redistribution and use in source and binary forms, with or without
5+
// modification, are permitted provided that the following conditions are met:
6+
//
7+
// * Redistributions of source code must retain the above copyright
8+
// notice, this list of conditions and the following disclaimer.
9+
//
10+
// * Redistributions in binary form must reproduce the above copyright
11+
// notice, this list of conditions and the following disclaimer in the
12+
// documentation and/or other materials provided with the distribution.
13+
//
14+
// * Neither the name of the {copyright_holder} nor the names of its
15+
// contributors may be used to endorse or promote products derived from
16+
// this software without specific prior written permission.
17+
//
18+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22+
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
// POSSIBILITY OF SUCH DAMAGE.
29+
// -- END LICENSE BLOCK ------------------------------------------------
30+
31+
//----------------------------------------------------------------------
32+
/*!\file
33+
*
34+
* \author Jacob Larsen jala@universal-robots.com
35+
* \maintainer Felix Exner feex@universal-robots.com
36+
* \date 2026-03-30
37+
*
38+
*/
39+
//----------------------------------------------------------------------
40+
41+
#ifndef UR_CLIENT_LIBRARY_PRIMARY_SAFETY_MODE_MESSAGE_H_INCLUDED
42+
#define UR_CLIENT_LIBRARY_PRIMARY_SAFETY_MODE_MESSAGE_H_INCLUDED
43+
44+
#include "ur_client_library/primary/robot_message.h"
45+
#include "ur_client_library/ur/datatypes.h"
46+
#include <variant>
47+
48+
namespace urcl
49+
{
50+
namespace primary_interface
51+
{
52+
/*!
53+
* \brief Representation of the primary interface's safety mode message
54+
*/
55+
class SafetyModeMessage : public RobotMessage
56+
{
57+
public:
58+
SafetyModeMessage() = delete;
59+
60+
/*!
61+
* \brief Creates a new SafetyModeMessage object to be filled from a package.
62+
*
63+
* \param timestamp Timestamp of the package
64+
* \param source The package's source
65+
*/
66+
SafetyModeMessage(uint64_t timestamp, int8_t source)
67+
: RobotMessage(timestamp, source, RobotMessagePackageType::ROBOT_MESSAGE_SAFETY_MODE)
68+
{
69+
}
70+
SafetyModeMessage(const SafetyModeMessage& pkg);
71+
72+
virtual ~SafetyModeMessage() = default;
73+
74+
/*!
75+
* \brief Sets the attributes of the package by parsing a serialized representation of the
76+
* package.
77+
*
78+
* \param bp A parser containing a serialized text of the package
79+
*
80+
* \returns True, if the package was parsed successfully, false otherwise
81+
*/
82+
virtual bool parseWith(comm::BinParser& bp);
83+
84+
/*!
85+
* \brief Consume this package with a specific consumer.
86+
*
87+
* \param consumer Placeholder for the consumer calling this
88+
*
89+
* \returns true on success
90+
*/
91+
virtual bool consumeWith(AbstractPrimaryConsumer& consumer);
92+
93+
/*!
94+
* \brief Produces a human readable representation of the package object.
95+
*
96+
* \returns A string representing the object
97+
*/
98+
virtual std::string toString() const;
99+
100+
int32_t message_code_;
101+
int32_t message_argument_;
102+
SafetyMode safety_mode_type_;
103+
uint32_t report_data_type_;
104+
105+
/* Content data type varies depending on report_data_type_
106+
Type 0 and 1: uint32_t
107+
Type 2: int32_t
108+
Type 3: float
109+
Type 4 : uint32_t (but should be displayed as hex, when printed) */
110+
std::variant<uint32_t, int32_t, float> report_data_;
111+
};
112+
} // namespace primary_interface
113+
114+
} // namespace urcl
115+
116+
#endif // ifndef UR_CLIENT_LIBRARY_PRIMARY_SAFETY_MODE_MESSAGE_H_INCLUDED

include/ur_client_library/ur/datatypes.h

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ enum class RobotMode : int8_t
4949
UPDATING_FIRMWARE = 8
5050
};
5151

52-
enum class SafetyMode : int8_t
52+
enum class SafetyMode : uint8_t
5353
{
5454
NORMAL = 1,
5555
REDUCED = 2,
@@ -61,7 +61,17 @@ enum class SafetyMode : int8_t
6161
VIOLATION = 8,
6262
FAULT = 9,
6363
VALIDATE_JOINT_ID = 10,
64-
UNDEFINED_SAFETY_MODE = 11
64+
UNDEFINED_SAFETY_MODE = 11,
65+
AUTOMATIC_MODE_SAFEGUARD_STOP = 12,
66+
SYSTEM_THREE_POSITION_ENABLING_STOP = 13,
67+
TP_THREE_POSITION_ENABLING_STOP = 14,
68+
IMMI_EMERGENCY_STOP = 15,
69+
IMMI_SAFEGUARD_STOP = 16,
70+
PROFISAFE_WAITING_FOR_PARAMETERS = 17,
71+
PROFISAFE_AUTOMATIC_MODE_SAFEGUARD_STOP = 18,
72+
PROFISAFE_SAFEGUARD_STOP = 19,
73+
PROFISAFE_EMERGENCY_STOP = 20,
74+
SAFETY_API_SAFEGUARD_STOP = 22
6575
};
6676

6777
enum class SafetyStatus : int8_t // Only available on 3.10/5.4
@@ -159,6 +169,26 @@ inline std::string safetyModeString(const SafetyMode& mode)
159169
return "VALIDATE_JOINT_ID";
160170
case SafetyMode::UNDEFINED_SAFETY_MODE:
161171
return "UNDEFINED_SAFETY_MODE";
172+
case SafetyMode::AUTOMATIC_MODE_SAFEGUARD_STOP:
173+
return "AUTOMATIC_MODE_SAFEGUARD_STOP";
174+
case SafetyMode::SYSTEM_THREE_POSITION_ENABLING_STOP:
175+
return "SYSTEM_THREE_POSITION_ENABLING_STOP";
176+
case SafetyMode::TP_THREE_POSITION_ENABLING_STOP:
177+
return "TP_THREE_POSITION_ENABLING_STOP";
178+
case SafetyMode::IMMI_EMERGENCY_STOP:
179+
return "IMMI_EMERGENCY_STOP";
180+
case SafetyMode::IMMI_SAFEGUARD_STOP:
181+
return "IMMI_SAFEGUARD_STOP";
182+
case SafetyMode::PROFISAFE_WAITING_FOR_PARAMETERS:
183+
return "PROFISAFE_WAITING_FOR_PARAMETERS";
184+
case SafetyMode::PROFISAFE_AUTOMATIC_MODE_SAFEGUARD_STOP:
185+
return "PROFISAFE_AUTOMATIC_MODE_SAFEGUARD_STOP";
186+
case SafetyMode::PROFISAFE_SAFEGUARD_STOP:
187+
return "PROFISAFE_SAFEGUARD_STOP";
188+
case SafetyMode::PROFISAFE_EMERGENCY_STOP:
189+
return "PROFISAFE_EMERGENCY_STOP";
190+
case SafetyMode::SAFETY_API_SAFEGUARD_STOP:
191+
return "SAFETY_API_SAFEGUARD_STOP";
162192
default:
163193
std::stringstream ss;
164194
ss << "Unknown safety mode: " << static_cast<int>(mode);

0 commit comments

Comments
 (0)