\documentclass[aspectratio=169]{beamer} \usepackage{mum-theme-beamer/mum-theme} \usepackage{graphicx} \input{code-block-settings.tex} \input{tikz-settings.tex} \title{Getting Started} \subtitle{Introduction into ROS} \date{24.10.2024} \author[\textbf{L. Alff}, N. Bauschmann, D.A. Duecker]{Lennart Alff, Nathalie Bauschmann, Daniel Duecker} \institute[TUHH]{Hamburg University of Technology} \renewcommand<>{\fbox}[1]{\alt#2{\fcolorbox{black}{white}{#1}}{\fcolorbox{white}{white}{#1}}} \begin{document} \begin{frame} \titlepage \end{frame} \begin{frame} \frametitle{Motivation} \fbox<3->{\begin{minipage}[]{0.45\textwidth} \onslide<1->{\textbf{Scenario:}} \begin{itemize} \item<1-> Mobile robot with a camera \end{itemize} \onslide<2->{\textbf{Goal:}} \begin{itemize} \item<2-> Autonomous navigation \end{itemize} \end{minipage}} \begin{minipage}[]{0.45\textwidth} \centering \includegraphics<1->[width=0.7\textwidth]{images/robot-bin.png} \end{minipage}\\ \vspace{2mm} \visible<4->{\begin{minipage}{\textwidth} \begin{tikzpicture}[node distance=8mm and 10mm] \node[fill=white] (camera) [rosnode]{Camera}; \node[fill=white] (image) [rostopic, right=of camera, xshift=10mm]{Image}; \node[fill=white] (localization) [rosnode, right=of image]{Localization}; \node[fill=white] (position) [rostopic, right=of localization]{Position}; \node[fill=white] (controller) [rosnode, below=of position]{Controller}; \node[fill=white] (motor-commands) [rostopic, left=of controller]{Motor Commands}; \node[fill=white] (motor-drivers) [rosnode, below=of camera]{Motor Drivers}; \begin{scope}[on background layer] \draw[thick, rounded corners=2mm, fill=blue!10] ($(camera.north west)+(-1.5, 0.5)$) rectangle ($(motor-drivers.south east)+(1.0,-0.5)$); \draw[thick, rounded corners=2mm, fill=green!10] ($(image.north west)+(-0.5, 0.5)$) rectangle ($(controller.south east)+(0.5,-0.5)$); \end{scope} \path[-latex, thick] (camera) edge (image) (image) edge (localization) (localization) edge (position) (position) edge (controller) (controller) edge (motor-commands) (motor-commands) edge (motor-drivers) ; % align needed for line break \node (env)[above=of camera, yshift=-4.5mm, align=center]{Interaction with\\Environment}; \node (env)[above=of localization, yshift=-4mm, align=center]{Internal Processing}; \end{tikzpicture} \end{minipage} } \end{frame} \begin{frame} \frametitle{How to do it?} \onslide<1->\textbf{\Large Naive Approach:} \begin{itemize} \item<2->Single monolithic program \begin{itemize} \item<3-> scales badly \item<3->hard to introspect/debug \item<3->what if multiple computers/devices used? \end{itemize} \end{itemize} \vspace{5mm} \onslide<4->\textbf{\Large Better Approach:} \begin{itemize} \item<5-> Use ROS $\Rightarrow$ write small programs \begin{itemize} \item<6->solve big problems by subdividision (divide \& conquer) \item<7->ROS brings a lot of utility \begin{itemize} \item<8-> plotting \item<9-> logging \item<10-> ... \end{itemize} \end{itemize} \end{itemize} \end{frame} \begin{frame} \frametitle{From Theory to Practice} \centering \textbf{\Huge From zero to depth!} \end{frame} \begin{frame}[fragile] \frametitle{Hello World} \begin{pythoncode} print('hello world!') \end{pythoncode} \end{frame} \begin{frame}[fragile] \frametitle{Hello World With Good Practice} \begin{pythoncode} def main(): print('hello world') if __name__ == '__main__': main() \end{pythoncode} \pause \vspace{5mm} \begin{itemize}[<+->] \item not strictly required but a convention \item analog to the \texttt{int main()} in C/C++ \end{itemize} \end{frame} \begin{frame}[fragile] \frametitle{First Contact with ROS} \begin{minipage}{0.48\textwidth} \begin{pythoncode} import rclpy from rclpy.node import Node def main(): rclpy.init() node = Node(node_name='my_node') rclpy.spin(node) if __name__ == '__main__': main() \end{pythoncode} \end{minipage} \begin{minipage}{0.48\textwidth} \begin{itemize}[<+->] \item minimal ROS program \item the generic \texttt{Node} does nothing on its own \begin{itemize}[<+->] \item not particularly useful \end{itemize} \end{itemize} \vspace{10mm} \pause \textbf{\Large $\Rightarrow$ we implement our own node!} \end{minipage} \end{frame} \begin{frame}[fragile] \frametitle{Our First Custom Node} \begin{minipage}{0.48\textwidth} \begin{pythoncode} import rclpy from rclpy.node import Node class DepthCalculatorNode(Node): def __init__(self): super().__init__(node_name='my_node') def main(): rclpy.init() node = DepthCalculatorNode() rlcpy.spin(node) if __name__ == '__main__': main() \end{pythoncode} \end{minipage} \begin{minipage}{0.48\textwidth} \pause \begin{itemize}[<+->] \small \item our custom node does exactly the same as the generic \texttt{rclpy.Node} $\rightarrow$ \textbf{nothing} \item this is the boilerplate code we need for every node we write \item doing nothing $\neq$ not beeing able to do something \end{itemize} \vspace{5mm} \pause \textbf{Provides us with these functions:} \begin{itemize}[<+->] \small \item \texttt{create\_publisher} \item \texttt{create\_subscription} \item \texttt{create\_timer} \item \texttt{get\_logger} \item \texttt{get\_clock} \end{itemize} \end{minipage} \end{frame} \begin{frame}[fragile] \frametitle{Receiving Pressure Measurements} \begin{pythoncode} import rclpy from rclpy.node import Node from sensor_msgs.msg import FluidPressure class DepthCalculatorNode(Node): def __init__(self): super().__init__(node_name='my_node') self.pressure_sub = self.create_subscription(FluidPressure, 'pressure', self.on_pressure, 1) def on_pressure(self, msg: FluidPressure): self.get_logger().info('Pressure measurement received') def main(): ... \end{pythoncode} \end{frame} \begin{frame}[fragile] \frametitle{Publishing Depth Values} \begin{pythoncode} import rclpy from hippo_msgs.msg import DepthStamped from rclpy.node import Node from sensor_msgs.msg import FluidPressure class DepthCalculatorNode(Node): def __init__(self): super().__init__(node_name='my_node') self.pressure_sub = self.create_subscription(FluidPressure, 'pressure', self.on_pressure, 1) self.depth_pub = self.create_publisher(DepthStamped, 'depth', 1) def on_pressure(self, msg: FluidPressure): self.get_logger().info('Pressure measurement received') pressure = msg.fluid_pressure depth = self.pressure_to_detph(pressure) depth_msg = DepthStamped() # do not forget the parentheses! depth_msg.header.stamp = msg.header.stamp depth_msg.depth = depth self.depth_pub.publish(depth_msg) def pressure_to_depth(self, pressure): depth = ??? return depth def main(): ... \end{pythoncode} \end{frame} \end{document}