ROS 2 Update

Deanna Hood, William Woodall
October 8, 2016
ROSCon 2016 Seoul
https://goo.gl/oCHR7H

Contents
ROS 2 overview
Overview of changes in the last year
Details of select features
Experience porting Turtlebot
Roadmaphttps://goo.gl/oCHR7H
2

ROS as we know it

3

●
●
●
●
●

Research applications
High-volume sensors
Complex kinematics
Lots of computation power
Ideal network connectivity

4

●
●
●
●

Multi-robot system
Small processors
Battery power
Unreliable network
connectivity

5

Goals of ROS 2

6

ROS 2

7

ROS 2+ ROS usability

less timespent heremeans

more time tospend here

8Architectural overview
User code

ROS client library API

9Architectural overview
User code

ROS client library API

DDS implementation= discovery + serialization + transport
10

ROSCon 2015 demos

Quality of Service demo forlossy networks using ROS 2
https://github.com/ros2/ros2/wiki/Tutorials

Bridge communication
between ROS 1 and ROS 2
Efficient intra-process
communication using ROS 2

Real-time safe codeusing ROS 2
ROS 2 on “bare-metal”
microcontrollers
11What’s new this year

12

Changes since ROSCon 2015: user-facing
Windows featureparity (alpha 2)

Fast RTPS added as asupported middleware(alpha 3)

Python client library(alpha 4)

Node “wait for service”
functionality (alpha 6)

Partial port of tf2
including the corelibraries (alpha 3)

32-bit and 64-bit ARM added
as experimentally supported
platforms (alpha 5)

Turtlebot demo using
ported code from
ROS 1 (alpha 7)13

Changes since ROSCon 2015
Windows featureparity (alpha 2)

Fast RTPS supported as
middleware (alpha 3)

32- and 64-bit ARM
experimentally supported
platforms (alpha 5)

Partial port of core tf2
libraries (alpha 3)

Node “wait for service”
functionality (alpha 6)

Python client library(alpha 4)

Turtlebot demo using
ported code from
ROS 1 (alpha 7)

14

Changes since ROSCon 2015
Windows featureparity (alpha 2)

Fast RTPS supported as
middleware (alpha 3)

32- and 64-bit ARM
experimentally supported
platforms (alpha 5)

Node “wait for service”
functionality (alpha 6)

ROS Client Library
implementation (rcl) (fromalpha 3, services alpha 5)

Refactored C++
client library touse rcl (alpha 6)

Partial port of core tf2
libraries (alpha 3)

ROS graph
events (alpha 6)

Python client library(alpha 4)

Turtlebot demo using
ported code from
ROS 1 (alpha 7)

Support for C messages (asopposed to C++) (alphas 4, 5, 7)

Improved support for large
messages (images) with Connext
and Fast RTPS (alpha 6, alpha 7)15

Changes since ROSCon 2015
Windows featureparity (alpha 2)

Fast RTPS supported as
middleware (alpha 3)

32- and 64-bit ARM
experimentally supported
platforms (alpha 5)

Node “wait for service”
functionality (alpha 6)

ROS Client Library
implementation (rcl) (fromalpha 3, services alpha 5)

Refactored C++
client library touse rcl (alpha 6)

Partial port of core tf2
libraries (alpha 3)

ROS graph
events (alpha 6)

Python client library(alpha 4)

Turtlebot demo using
ported code from
ROS 1 (alpha 7)

Support for C messages (asopposed to C++) (alphas 4, 5, 7)

Improved support for large
messages (images) with Connext
and Fast RTPS (alpha 6, alpha 7)16

Changes since ROSCon 2015
Windows featureparity (alpha 2)

Fast RTPS supported as
middleware (alpha 3)

32- and 64-bit ARM
experimentally supported
platforms (alpha 5)

Node “wait for service”
functionality (alpha 6)

ROS Client Library
implementation (rcl) (fromalpha 3, services alpha 5)

Refactored C++
client library touse rcl (alpha 6)

Partial port of core tf2
libraries (alpha 3)

ROS graph
events (alpha 6)

Python client
library (alpha 4)
Turtlebot demo using
ported code from
ROS 1 (alpha 7)

Support for C messages (asopposed to C++) (alphas 4, 5, 7)

Improved support for large
messages (images) with Connext
and Fast RTPS (alpha 6, alpha 7)17

Changes since ROSCon 2015
Windows featureparity (alpha 2)

Fast RTPS supported
as middleware(alpha 3)

32- and 64-bit ARM
experimentally supported
platforms (alpha 5)

Node “wait for service”
functionality (alpha 6)

ROS Client Library
implementation (rcl) (fromalpha 3, services alpha 5)

Refactored C++
client library touse rcl (alpha 6)

Partial port of core tf2
libraries (alpha 3)

ROS graph
events (alpha 6)

Python client library(alpha 4)

Turtlebot demo using
ported code from
ROS 1 (alpha 7)

Support for C messages (asopposed to C++) (alphas 4, 5, 7)

Improved support for large
messages (images) with Connext
and Fast RTPS (alpha 6, alpha 7)18

Architectural overview

User code

ROS client library API

DDS impl A

or

DDS impl B

or

...

19

Architectural overview

User code

ROS client library API

DDS
agnostic

ROS middleware API

DDS impl A

or

DDS impl B

or

...

ROS
agnostic

20

Architectural overview

User code

ROS client library API

DDS
agnostic

ROS middleware API
ROS
agnostic
DDS impl A

or

DDS impl B

or

...

21

Architectural overview
User code

ROS client library API

DDS
agnostic

ROS middleware API
RMW impl A

RMW impl B...
ROS
agnostic

DDS impl A

or

DDS impl B

or

...
22

Architectural overview
User code

ROS client library API

DDS
agnostic

ROS middleware API
Fast RTPS
RMW impl

Fast RTPS

RMW impl B...
ROS
agnosticor

DDS impl B

or

...
23

Supported vendors until October 2016

User code

ROS client library API

ROS middleware API
eProsima
Fast RTPS

RTI
Connext

Connext
Dynamic

PrismTech
OpenSplice24Supported vendors since October 2016

User code

ROS client library API

ROS middleware API
eProsima
Fast RTPS
Default

RTI
Connext

Connext
Dynamic

PrismTech
OpenSplice

Development paused

25

Why eProsima’s Fast RTPS?
● Changed the license June 2016:○ LGPL -> Apache 2.0● Code on GitHub○ https://github.com/eProsima/Fast-RTPS● Responsive to issues and pull requests● Added features needed to support ROS 2○

Fragmentation of large messages○

Graph change notifications● CMake buildsystem

26

Changes since ROSCon 2015
Windows featureparity (alpha 2)

Fast RTPS supported as
middleware (alpha 3)

32- and 64-bit ARM
experimentally supported
platforms (alpha 5)

Node “wait for service”
functionality (alpha 6)

ROS Client Library
implementation (rcl) (fromalpha 3, services alpha 5)

Refactored C++
client library touse rcl (alpha 6)

Partial port of core tf2
libraries (alpha 3)

ROS graph
events (alpha 6)

Python client
library (alpha 4)
Turtlebot demo using
ported code from
ROS 1 (alpha 7)

Support for C messages (asopposed to C++) (alphas 4, 5, 7)

Improved support for large
messages (images) with Connext
and Fast RTPS (alpha 6, alpha 7)

27

ROS client libraries

talker.py

listener.cpp

28

ROS client libraries

talker.py

listener.cpp

Python ROS
client library

C++ ROSclient library

29

ROS client libraries
User Coderclcpprclpy

ROS middleware interface(rmw)
DDS vendor

30

ROS client libraries
User Coderclcpprclpy

ROS middleware interface(rmw)

Names &
namespaces

Time

Parameters

Consolelogging

Threading model

Intra-process
communication

DDS vendor

31

ROS client libraries
User Coderclcpprclpy

ROS middleware interface(rmw)

Names &
namespaces

Time

Parameters

Consolelogging

Threading model

Intra-process
communication

DDS vendor

32

ROS client libraries
Threading model

Intra-process
communication

ROS client library (rcl)Names &
namespaces

Time

ROS middleware interface(rmw)

Parameters

Consolelogging

User Coderclcpprclpy

DDS vendor
33

ROS client libraries
node.cpp

node.pynode.cs

node.java

rclcpp

rclpy

rclcs

rcljava

...

rcl

rmw
https://github.com/firesurfer/rclcs

DDS vendor

https://github.com/esteve/ros2_java34

Tracing talker-listenerConsider this talker-listener example:

talker.py

publish/chattersubscribe

listener.cpp

35Tracing talker-listenerrclpy.init()
talker.py

publish/chattersubscribe

node = rclpy.create_node('talker')
chatter_pub = node.create_publisher(
std_msgs.msg.String, 'chatter')
msg = std_msgs.msg.String()
i = 1

listener.cpp

while True:
msg.data = 'Hello World: {0}'.format(i)i += 1
print('Publishing: "{0}"'.format(msg.data))chatter_pub.publish(msg)36

Tracing talker-listenerrclpy.init()
talker.py

rclpy

publish/chattersubscribe

node = rclpy.create_node('talker')
chatter_pub = node.create_publisher(
std_msgs.msg.String, 'chatter')
msg = std_msgs.msg.String()
i = 1

listener.cpp

while True:
msg.data = 'Hello World: {0}'.format(i)i += 1
print('Publishing: "{0}"'.format(msg.data))chatter_pub.publish(msg)37

Tracing talker-listenertalker.py

rclpy

publish/chattersubscribe

listener.cpp

static PyObject *rclpy_publish(PyObject * Py_UNUSED(self), PyObject * args) {PyObject * pypublisher; // populated from args
PyObject * pymsg;// populated from args// ...
void * raw_ros_message = convert_from_py(pymsg);
rcl_ret_t ret = rcl_publish(publisher, raw_ros_message);if (ret != RCL_RET_OK) {// error handling}// ...
}

38

Tracing talker-listenertalker.py

rclpy

publish/chattersubscribe

listener.cpp

static PyObject *rclpy_publish(PyObject * Py_UNUSED(self), PyObject * args) {PyObject * pypublisher; // populated from args
PyObject * pymsg;// populated from args// ...
void * raw_ros_message = convert_from_py(pymsg);

rcl

rcl_ret_t ret = rcl_publish(publisher, raw_ros_message);if (ret != RCL_RET_OK) {// error handling}// ...
}

39

Tracing talker-listenertalker.py

rclpy

rcl

publish/chattersubscribe

listener.cpp

rcl_ret_t
rcl_publish(
const rcl_publisher_t * publisher,const void * ros_message){// ...
ret = rmw_publish(publisher->impl->rmw_handle, ros_message);if (ret != RMW_RET_OK) {// error handling}
return RCL_RET_OK;}

40

Tracing talker-listenertalker.py

rclpy

rcl

rmw

publish/chattersubscribe

listener.cpp

rcl_ret_t
rcl_publish(
const rcl_publisher_t * publisher,const void * ros_message){// ...
ret = rmw_publish(publisher->impl->rmw_handle, ros_message);if (ret != RMW_RET_OK) {// error handling}
return RCL_RET_OK;}

41

Tracing talker-listenertalker.py

publish/chattersubscribe

listener.cpp

rclpy

rcl

rmw

rmw_ret_t
rmw_publish(
const rmw_publisher_t * publisher,const void * ros_message);42

Tracing talker-listenertalker.py

publish/chattersubscribe

listener.cpp

rclpy

rcl

rmw
rmw impl
DDS Vendor

IPC

DDS Vendor
43

Tracing talker-listenertalker.py

publish/chattersubscribe

listener.cpp

rclpy

rcl

rmw
rmw_fastrtps_cpp

Fast RTPS

IPC

Fast RTPS
44

Tracing talker-listenertalker.py

rclpy

rmw_ret_t
rmw_publish(/chatterpublishsubscribe
listener.cpp
const rmw_publisher_t * publisher, const void * ros_message){// ...
eprosima::fastcdr::FastBuffer buffer;eprosima::fastcdr::Cdr ser(buffer);
PublisherImpl * info = (PublisherImpl *)publisher->data;
if(_serialize_ros_message(ros_message, ser, /* ... */)) {if(info->publisher_->write(&ser)) // Fast RTPS publisher
return RMW_RET_OK;
else// ... publish error handling}else
Fast RTPS// ... serialize error handling

rcl

rmw
rmw_fastrtps_cpp}

45Tracing talker-listenertalker.py

rclpy

rmw_ret_t
rmw_publish(/chatterpublishsubscribe
listener.cpp
const rmw_publisher_t * publisher, const void * ros_message){// ...
eprosima::fastcdr::FastBuffer buffer;eprosima::fastcdr::Cdr ser(buffer);
PublisherImpl * info = (PublisherImpl *)publisher->data;
if(_serialize_ros_message(ros_message, ser, /* ... */)) {if(info->publisher_->write(&ser)) // Fast RTPS publisher
return RMW_RET_OK;
else// ... publish error handling}else
Fast RTPS// ... serialize error handling

rcl

rmw
rmw_fastrtps_cpp}

46Tracing talker-listenertalker.py

publish/chattersubscribe

listener.cpp

rclpy

rcl

rmw

“Hello World”

rmw_fastrtps_cpp

Fast RTPS

IPC

Fast RTPS
47

Tracing talker-listenervoid/chatterpublishsubscribe
chatter_callback(const
std_msgs::msg::String::SharedPtr
msg) {
talker.py
std::cout << "I heard: [" << msg->data << "]" << std::endl;}

listener.cpp

int
rclpy
main(int argc, char * argv[]) {rclcpp::init(argc, argv);
auto nodercl = rclcpp::Node::make_shared("listener");
auto sub = node->create_subscription<std_msgs::msg::String>("chatter",rmw chatter_callback, rmw_qos_profile_default);
rmw_fastrtps_cpp

rmw_publish()rclcpp::spin(node);}

Fast RTPS“Hello World”

Fast RTPS
IPC

Fast RTPS
48

Tracing talker-listenervoid/chatterpublishsubscribe
chatter_callback(const
std_msgs::msg::String::SharedPtr
msg) {
talker.py
std::cout << "I heard: [" << msg->data << "]" << std::endl;
}
int
rclpy
main(int argc, char * argv[]) {rclcpp::init(argc, argv);

listener.cpp

rclcppauto nodercl = rclcpp::Node::make_shared("listener");
auto sub = node->create_subscription<std_msgs::msg::String>("chatter",rmw chatter_callback, rmw_qos_profile_default);
rmw_fastrtps_cpp

rmw_publish()rclcpp::spin(node);}

Fast RTPS“Hello World”

Fast RTPS
IPC

Fast RTPS
49

Tracing talker-listenertalker.py

rclpy

rcl

rclcpp::spin()
publish/chattersubscribe

wait for “work”

listener.cpp

rclcppDoes
subscription
have data?

rmw
rmw_fastrtps_cpp

rmw_publish()Yes

Fast RTPS“Hello World”
execute_subscription()
Fast RTPS
IPC

Fast RTPS
50

Tracing talker-listenertalker.py

publish/chattersubscribe

listener.cpp

rclpy
a

s

rcl

n

ha

t
da

io
pt

ri
c
s
ub

S

rmw

“Hello World”

rmw_fastrtps_cpp

Fast RTPS

IPC

Fast RTPS
51

Tracing talker-listenertalker.py

rclpy

rcl

rclcpp::spin()
publish/chattersubscribe

wait for “work”

listener.cpp

rclcppDoes
subscription
have data?

rmw
rmw_fastrtps_cpp

rmw_publish()Yes

Fast RTPS“Hello World”
execute_subscription()
Fast RTPS
IPC

Fast RTPS
52

Tracing talker-listenervoid execute_subscription(/* ... */ subscription)
rclcpp::spin()/chatterpublish{ talker.py
std::shared_ptr<void> message =subscription->create_message();subscribe

wait for “work”

rclpy
auto
ret = rcl_take(subscription->get_subscription_handle(),message.get(), /* ... */);
if (ret == RCL_RET_OK)
{
Does
rcl
subscription
subscription->handle_message(message,/* ... */);
} else { /* haveerrordata?
handling */ }listener.cpp

rclcpp}rmw
rmw_fastrtps_cpp

rmw_publish()Yes

Fast RTPS“Hello World”
execute_subscription()
Fast RTPS
IPC

Fast RTPS
53

Tracing talker-listenervoid execute_subscription(/* ... */ subscription)
rclcpp::spin()/chatterpublish{ talker.py
std::shared_ptr<void> message =subscription->create_message();subscribe

wait for “work”

rclpy
auto
ret = rcl_take(subscription->get_subscription_handle(),message.get(), /* ... */);
if (ret == RCL_RET_OK)
{
Does
rcl
subscription
subscription->handle_message(message,/* ... */);
} else { /* haveerrordata?
handling */ }listener.cpp

rclcpprcl}

rmw
rmw_fastrtps_cpp

rmw_publish()Yes

Fast RTPS“Hello World”
execute_subscription()
Fast RTPS
IPC

Fast RTPS
54

Tracing talker-listener
rcl_ret_t
rcl_take(/chatterpublishsubscribe
talker.py
const rcl_subscription_t * subscription,
void * ros_message, /* ... */){// ...
rclpy
bool taken = false;
rmw_ret_t ret = rmw_take(subscription->impl->rmw_handle, ros_message, &taken);
if (ret != RMW_RET_OK) {rcl // ... error handling}if (!taken) {return RCL_RET_SUBSCRIPTION_TAKE_FAILED;
rmw}“Hello World”
rmw_fastrtps_cpp
return RCL_RET_OK;
rmw_publish()
Fast RTPS}

Fast RTPS

IPC

listener.cpp

rclcpprcl

Fast RTPS
55

Tracing talker-listener
rcl_ret_t
rcl_take(/chatterpublishsubscribe
talker.py
const rcl_subscription_t * subscription,
void * ros_message, /* ... */){// ...
rclpy
bool taken = false;
rmw_ret_t ret = rmw_take(subscription->impl->rmw_handle, ros_message, &taken);
if (ret != RMW_RET_OK) {rcl // ... error handling}if (!taken) {return RCL_RET_SUBSCRIPTION_TAKE_FAILED;
rmw}“Hello World”
rmw_fastrtps_cpp
return RCL_RET_OK;
rmw_publish()
Fast RTPS}

Fast RTPS

IPC

listener.cpp

rclcpprcl

rmw

Fast RTPS
56

Tracing talker-listenertalker.py

publish/chattersubscribe

rclpy

listener.cpp

rclcpprmw_ret_t
rmw_take(rcl
const rmw_subscription_t * subscription,
void * ros_message,
rmwbool * taken);“Hello World”

rmw_fastrtps_cpp

Fast RTPS

IPC

rcl

rmw

Fast RTPS
57

Tracing talker-listener
rmw_ret_t/chatterpublishsubscribe
rmw_take(talker.py
const rmw_subscription_t * subscription,
void * ros_message, bool * taken){*taken = false;
rclpy
SubscriptionImpl * info = (SubscriptionImpl *)subscription->data;

listener.cpp

rclcppeprosima::fastcdr::FastBuffer buffer;
SampleInfo_t sinfo;

rcl

if(info->subscriber_->takeNextData(&buffer, &sinfo)) {if(sinfo.sampleKind == ALIVE) { // actually contains data
_deserialize_ros_message(&buffer, ros_message, /* ... */);
rmw
*taken = true;“Hello World”
rmw_fastrtps_cpp}
rmw_publish()
rmw_take()
Fast RTPS}}
Fast RTPS
IPC
Fast RTPS

rcl

rmw
rmw_fastrtps_cpp

58Tracing talker-listener
rmw_ret_t/chatterpublishsubscribe
rmw_take(talker.py
const rmw_subscription_t * subscription,
void * ros_message, bool * taken){*taken = false;
rclpy
SubscriptionImpl * info = (SubscriptionImpl *)subscription->data;

listener.cpp

rclcppeprosima::fastcdr::FastBuffer buffer;
SampleInfo_t sinfo;

rcl

if(info->subscriber_->takeNextData(&buffer, &sinfo)) {if(sinfo.sampleKind == ALIVE) { // actually contains data
_deserialize_ros_message(&buffer, ros_message, /* ... */);
rmw
*taken = true;“Hello World”
rmw_fastrtps_cpp}
rmw_publish()
rmw_take()
Fast RTPS}}
Fast RTPS
IPC
Fast RTPS

rcl

rmw
rmw_fastrtps_cpp

59Tracing talker-listenervoid/chatterpublishsubscribe
talker.py
chatter_callback(const std_msgs::msg::String::SharedPtr msg) {std::cout << "I heard: [" << msg->data << "]" << std::endl;}

listener.cpp

rclpy

rclcpp

rcl

rcl

rmw

rmw

“Hello World”

rmw_fastrtps_cpp

Fast RTPS

IPC

rmw_fastrtps_cpp

Fast RTPS
60

Changes since ROSCon 2015
Windows featureparity (alpha 2)

Fast RTPS supported as
middleware (alpha 3)

32- and 64-bit ARM
experimentally supported
platforms (alpha 5)

Node “wait for service”
functionality (alpha 6)

ROS Client Library
implementation (rcl) (fromalpha 3, services alpha 5)

Refactored C++
client library touse rcl (alpha 6)

Partial port of core tf2
libraries (alpha 3)

ROS graph
events (alpha 6)

Python client library(alpha 4)

Turtlebot demousing ported codefrom ROS 1 (alpha 7)

Support for C messages (asopposed to C++) (alphas 4, 5, 7)

Improved support for large
messages (images) with Connext
and Fast RTPS (alpha 6, alpha 7)

61

Porting of Turtlebot to ROS 2● Minimum viable demo (https://github.com/ros2/turtlebot2_demo)○ Kobuki driver○ Astra driver○ Joystick driver○ Follower node

https://orbbec3d.com/

http://kobuki.yujinrobot.com/
62

Porting of Turtlebot to ROS 2● Kobuki driver○ Used existing non-ROS dependencies○ Replaced ROS 1 wrapper with ROS 2 wrapper● Astra driver○ Forked and ported existing ROS 1 driver to ROS 2● Joystick driver○ Wrote a simple joystick program from scratch (no porting)● Follower node
○ Forked and ported existing ROS 1 node
● ROS 1 ⇔ ROS 2 bridge for visualization
63

Porting Experiments● ROS 1 “shim” (https://github.com/codebot/ros1_shim)○ Some things (like the astra driver) needed some deep
features (e.g. custom serialization)○ Hard to find the right strata in the interfaces to shim

64

Porting Experiments● catment (https://github.com/ros2/ros2/wiki/catment)○ Find ways to modify each to make them more similar■ In order to minimize conversion effort○ Mixing catkin (ROS 1) and ament (ROS 2)■ To avoid converting unless necessary○ Non-homogeneous workspace■ Building catkin and ament packages at the same time
○ Ideal: one build tool for both■ ament vs catkin not unlike catkin vs plain cmake65

Porting Experiments● catment continued...
○ Conceptual details to work out:
■ setup.*sh files in root of workspace● Currently required by catkin● Optional for ament■ devel-space● ament uses “symlink install” instead■ Avoiding confusion in documentation○ Make catkin more like ament? (and vice versa?)

66

Roadmap●●

Beta 1 - End of the Year
○ Composition■ may use pluginlib and class_loader from ROS 1 for C++○ QoS benchmarks■ for example: unreliable comms, illustrated by wifi out-and-back○ Design documents○ Tutorials and examples○ "rostopic list", "rostopic echo", and friends○ Bridging services to/from ROS1 (in addition to topics)
Nice to have by Beta 1:○ Console logging■ think “rosconsole”○ Orchestration■ think “roslaunch + verification & dynamic behavior”

67Pointers●●

ROS 2 wiki: https://github.com/ros2/ros2/wiki○ Installation instructions○ Tutorials○ How to contribute○ Current status○ Roadmap
Developer docs (work in progress):○●

https://github.com/ros2/ros_core_documentation/blob/master/source/developer_overview.rst○ Architecture overview○ Links to API docs
Design documents: http://design.ros2.org/○ Articles about various subjects○ On going discussions on the issue tracker:
https://github.com/ros2/design

68Questions

https://goo.gl/oCHR7H
69

“Hour Glass” Pattern
API size{client libraries}
rcl
rcl impl
LOC

rmw{rmw impl}{DDS vendor}70

“Hour Glass” Pattern - C++ with Fast RTPS
API size

rclcpprcl
rcl impl
LOC

rmw
rmw_fastrtps_cpp

Fast RTPS71

“Hour Glass” Pattern - Python with RTI Connext
API sizerclpy
rcl
rcl impl
LOC

rmw
rmw_connext_cpp

RTI Connext72

“Hour Glass” Pattern - Python with RTI Connext
API sizerclpy
rcl
rcl impl

CommonLOC

rmw
rmw_connext_cpp

RTI Connext73

“Hour Glass” Pattern - Python with RTI Connext
API sizerclpy
rcl
rcl impl
LOC

rmw
rmw_connext_cpp

RTI Connext

VendorSpecific

74