# Tutorial 1
## Your first publisher and subscriber

!!!info
Terminology note: A "node" refers to one of these publisher or subscriber units -- you can think of them as just a program that does "something" within ROS. A "topic" refers to the place a publisher publishes its messages for subscribers to read. For example, a camera might publish images to ``/image`` -- then any subscriber can listen to ``/image`` and pick up any image from the camera. Anything sent by a publisher node to its respective topic is called a "message"
!!!

### Prerequisites
All that follows is assuming you have successfully completed [!ref icon=":hammer_and_wrench:" name="Setup"](/f1tenth/setup.md) and you have a working docker container. If you haven't done so, take the time to do so now. If you have hit a roadblock in the setup, ask a lead for assistance (or google it and try to figure it out on your own...)

---

## Step 0: Create the package

In order to write our nodes, we need to first create its package. This is will simply give us the scaffolding for a project, so that we have all the resources we need. 

If you haven't already, go ahead and launch your docker and make sure you're attached to it (you should see that big yellow ``[DOCKER]`` on your command prompt). Go ahead and navigate to your ``src`` directory (``cd src``). Then, to create the package, simply run

```
ros2 pkg create --build-type ament_python py_pubsub
```

Where ``ros2`` is the executable we're running, ``pkg`` tells ``ros2`` we want to do something with a package, ``create`` tells it we want to create one, ``--build-type ament_python`` tells the package creation tool that we want a pure python package, and ``py_pubsub`` is the name of the package.

---

Now, navigate to the source directory of your new package at ``py_pubsub/py_pubsub/``, and create/open a new file called `publisher_member_function.py`.

!!!info
**Note**: Files for this and subsequent projects can be edited in a number of ways. Any IDE can edit these files as the docker environment has ``src/`` outside the docker being the same as ``src/`` inside the docker -- just navigate to where you have this tutorial and open the file you want to edit! 
!!!

We'll now write the node step by step. You can either read these sections step by step and then copy based off the full file shown later, or you can copy with each step by step. 

!!!secondary
**Tip**: type the code yourself even if you're typing exactly what's written here -- it will help you be familiar with what you've written.
!!!

---
### Step 0.5: What are we building?

We are going to build a simple publisher node and subscriber node. The publisher node will publish some message to 'topic' on some repeated timed interval, and the subscriber node will read these messages and print them out for us.

---

## Step 1: Writing the publisher

Let's get coding!

We'll start by importing the necessary libraries: ``rclpy`` (**R**OS **C**lient **L**ibrary for **Py**thon) and the object ``Node`` from ``rclpy.node``:
```python
import rclpy
from rclpy.node import Node
```

Next, we need to make sure we have the object we want to be able to send with our publisher. So, we'll go ahead and import the standard "String" message since all we want to send is text:
```python
from std_msgs.msg import String
```

Now we begin the body of the code, which we'll start by creating our publisher node's python class. Python's inheritance is out of the scope of this tutorial, and isn't needed to understand what's going on, but for those who want to take the time to learn it can look at a good inheritance explainer by GeeksForGeeks: [!ref target="blank" text="Inheritance in Python"](https://www.geeksforgeeks.org/inheritance-in-python/#) You'll see ``Node`` in the parentheses of the class definition because we want our publisher node to be built on top of the ``Node`` class:
```python
class MinimalPublisher(Node):
```

All python classes require a constructor to be defined via ``__init__(self)``, and this node is no different. 

We'll start by calling the constructor of the class we're inheriting from and giving our node a name via ``super().__init__('minimal_publisher')``. 

Next, we'll initialize a publisher object and make it a class member by adding ``self.publisher = self.create_publisher(String, 'topic', 10)``. This makes ``self.publisher`` our node's publisher object (which we'll call to publish) by calling the inherited class ``Node``'s function ``create_publisher(msg_type, topic_name, queue_size)`` (the ``queue_size`` parameter denotes the allowed amount of queued messages if a subscriber isn't receiving them fast enough).

Finally, we'll define our timer interval, create the timer object, and initialize a member index counter to 0. 

All together, we get 
```python
def __init__(self):
    super().__init__('minimal_publisher')
    self.publisher_ = self.create_publisher(String, 'topic', 10)
    
    timer_period = 0.5
    
    # self.timer_callback refers to a function we'll define later that we want the timer
    # object to call every time the interval completes
    self.timer = self.create_timer(timer_period, self.timer_callback)
    self.i = 0
```

Now we want to define ``timer_callback()`` so that the timer has something to do. Here we'll create a new message by initializing a ``String()`` object, we'll define the data part of the message, then we'll call ``self.publisher_`` to publish our message, then finishing the function by logging that we've published a message and incrementing our counter variable:
```python
def timer_callback(self):
        msg = String()
        msg.data = f"Hello world: {self.i}"
        self.publisher_.publish(msg)
        self.get_logger().info(f"Publishing: \"{msg.data}\"")
        self.i += 1
```

Finally, we'll create the ``main`` function for the script, so that python knows what to do when ROS runs our package. Unlike the previous two functions, this is not supposed to be a part of the ``MinimalPublisher`` node.

Here we'll initialize ``rclpy``, initialize our publisher node, "spin" up the node (start it), and then tell it to destroy and shutdown once it's done:
```python
def main(args=None):
    rclpy.init(args=args)
    
    minimal_publisher = MinimalPublisher()
    
    rclpy.spin(minimal_publisher)
    
    # We won't reach this point until the publisher has been told to stop running
    minimal_publisher.destroy_node()
    
    rclpy.shutdown()

if __name__ == "__main__":
    main()
```

Just like that, our publisher is done! All together, this script should look like this:
```python

import rclpy
from rclpy.node import Node

from std_msgs.msg import String


class MinimalPublisher(Node):
    def __init__(self):
        super().__init__('minimal_publisher')
        self.publisher_ = self.create_publisher(String, 'topic', 10)
        timer_period = 0.5
        self.timer = self.create_timer(timer_period, self.timer_callback)
        self.i = 0
        
    def timer_callback(self):
        msg = String()
        msg.data = f"Hello world: {self.i}"
        self.publisher_.publish(msg)
        self.get_logger().info(f"Publishing: \"{msg.data}\"")
        self.i += 1
        
        
def main(args=None):
    rclpy.init(args=args)
    
    minimal_publisher = MinimalPublisher()
    
    rclpy.spin(minimal_publisher)
    
    minimal_publisher.destroy_node()
    
    rclpy.shutdown()


if __name__ == "__main__":
    main()

```

---

## Step 2: Writing the subscriber

In the same place as the previous file, now create the file ``subscriber_member_function.py``. Go ahead and open it and let's start writing!

This node is almost the exact same as the publisher, with a few select differences. Rather than step by step through the entire script again, we're just going to speak on the differences.

The imports are the exact same, but the class has a different name now -- ``MinimalSubscriber``. Thus, the first section is simply 
```python
import rclpy
from rclpy.node import Node

from std_msgs.msg import String

class MinimalSubscriber(Node):
```

Within the constructor, we'll want to change the name to 'minimal_subscriber', and we'll want to define a subscriber object rather than a publisher object, giving us:
```python
    def __init__(self):
        super().__init__('minimal_subscriber')
        self.subscription = self.create_subscription(
            String, # Message type
            'topic', # topic name
            self.listener_callback, # Function to call whenever we receive a message
            10 # Queue size
        )

        self.subscription
```

Now completely different from the publisher is the callback function. We don't care about any timers or publishers, we just want to print out our message whenever we get it. The callback you put into the ``self.create_subscription`` function will have one parameter (aside from ``self`` as it is a member function), that parameter being the received message. Thus, our listener callback should look like the following:

```python
    def listener_callback(self, msg):
        self.get_logger().info(f"Recieved msg: \"{msg.data}\"")
```

Finally, the main function and body are the exact same, just changing names to reflect the fact that this is the subscriber and not the publisher.

The full script should look like this:
```python
import rclpy
from rclpy.node import Node

from std_msgs.msg import String

class MinimalSubscriber(Node):
    def __init__(self):
        super().__init__('minimal_subscriber')
        self.subscription = self.create_subscription(
            String,
            'topic',
            self.listener_callback,
            10
        )
        
        self.subscription
        
    def listener_callback(self, msg):
        self.get_logger().info(f"Recieved msg: \"{msg.data}\"")
        
        
def main(args=None):
    rclpy.init(args=args)
    
    minimal_subscriber = MinimalSubscriber()
    
    rclpy.spin(minimal_subscriber)
    
    minimal_subscriber.destroy_node()
    rclpy.shutdown()
    
    
if __name__ == "__main__":
    main()
```

Now that we have both the publisher and subscriber, we need to configure the package to make sure we have all the required dependencies to run it.

---

## Step 3: Configuring the package

Contained one folder back from where we stored our source code just now are a couple important files, ``setup.py``, ``setup.cfg``, and ``package.xml``. 

``setup.py`` tells Python how your project is structured and what files to run if the project is asked to execute some code, ``setup.cfg`` are various ROS building arguments, and ``package.xml`` contains important information about the package as a whole, like the project's dependencies. We have some dependencies to add, so go ahead and open ``package.xml`` for editing.

Underneath some metainfo definitions like ``<license>``, you should see a handful of lines which read along the lines of ``<test_depend> (something) </test_depend>``. Above those statements and below the metainfo, we want to add our two dependencies: 
```xml
  <exec_depend>rclpy</exec_depend>
  <exec_depend>std_msgs</exec_depend>
```

Next, open up ``setup.py`` so that we can add "entrypoints" -- essentially telling python what code to run if it's asked to run "talker" or "listener". Add the following to "entry_points":
```python
entry_points={
        'console_scripts': [
                'talker = py_pubsub.publisher_member_function:main',
                'listener = py_pubsub.subscriber_member_function:main',
        ]},
``` 

---

## Step 4: Building & Running

To start, navigate to the root of the workspace (``/ros2_ws/``).

The docker image *should* automatically do the following step, but it's good to do yourself anyway for redundancy. We'll just make sure we have all of our ROS dependencies by running
```
rosdep install -i --from-path src --rosdistro iron -y
```

Next (still in the workspace root), we'll build the project via 
```
colcon build --packages-select py_pubsub
```
and then
```source install/setup.bash```

!!!primary Important
To be able to run both nodes at the same time and see each of their outputs, we'll need to open another terminal. To do this, we'll use ``tmux`` (terminal multi-plexer) which is just a way to split one terminal into multiple other "panes" of terminals. To start simply type ``tmux`` in the terminal and run it. You should've opened into a new terminal and there should be a green bar on the bottom of the terminal. To split the view and add another pane, use the key chord ``Ctrl+b, "`` (Ctrl+b then quotation marks (shift+')) for a horizontal pane or ``Ctrl+b, %`` for a vertical pane. To switch between these panes you can just use the key chord ``Ctrl+b, <arrow direction>`` depending on which direction you want to go.
!!!

Now with tmux up and running, in one pane go ahead and run 
```
ros2 run py_pubsub talker
``` 
to start the publisher. On that pane, you should start seeing some output like 
```
[INFO] [minimal_publisher]: Publishing: "Hello World: 0"
[INFO] [minimal_publisher]: Publishing: "Hello World: 1"
...
```

Next, move over to your other pane and run ``ros2 run py_pubsub listener`` to start the subscriber. Once this node is up and running, it should begin outputting the messages it's receiving from the publisher at that moment. For example:
```
[INFO] [minimal_subscriber]: Received msg:: "Hello World: 20"
[INFO] [minimal_subscriber]: Received msg:: "Hello World: 21"
[INFO] [minimal_subscriber]: Received msg:: "Hello World: 22"
[INFO] [minimal_subscriber]: Received msg:: "Hello World: 23"
[INFO] [minimal_subscriber]: Received msg:: "Hello World: 24"
```

Congrats, your first publisher and subscriber nodes are up and running! To turn them off just ``Ctrl+c`` in their respective panes. You can exit tmux by either running ``exit`` within each pane or using ``Ctrl+b, x`` to close each pane (it will prompt asking if you want to close the pane, to which you will confirm by just pressing 'y').
