Make your own Robot

Note

This assumes familiarity with object-oriented programming (particularly inheritance and abstract classes).

The Gridsim library provides a Robot class that manages underlying behavior and drawing of robots, making it easy for you to quickly implement your own functionality and algorithms.

In fact, the default Robot class is an abstract class; you must implement your own Robot subclass. There are five abstract Robot methods that you must implement in your own class. (Inputs and outputs are not shown.)

  • move(): Step-wise movement of the robot on the grid

  • comm_criteria(): Distance-based criteria for whether or not another robot is within communication range of this robot.

  • receive_msg(): Code that is run when a robot receives a message

  • init(): Code that is run once when the robot is created

  • loop(): Code that is run in every step of the simulation

It also includes an optional method you may want to implement in your subclass:

  • msg_received(): Code that is run when a robot’s successfully sends a message to another robot.

In general, you will likely want to implement your own robots with an additional two layers of subclasses, as seen in the graph below. This allows you to separate the physical robot platform you are representing from the algorithms/code you are running on that platform.

digraph example {
  rankdir="BT"
  robot [label="gridsim.Robot", href="http://sphinx-doc.org", target="_top" shape="record"];
  gr [label="gridsim.GridRobot" shape="record"];
  yr [label="{YourRobot|Custom Robot}" shape="record"];
  ar [
    label="{YourAlgorithm|YourRobot running custom algorithm}"
    shape="record"];
  rr [label="{RandomRobot|GridRobot doing random movement}"
      shape="record"];
  cr [label="{AnotherAlgorithm|GridRobot running different code}"
      shape="record"];

  yr -> robot;
  gr -> robot;
  rr -> gr;
  cr -> gr;
  ar -> yr;

  subgraph cluster_subs {
    label="Robot Platforms"
    rank="same"
    yr
    gr
  }
}

First, you create a subclass that represents the physical robot system you are representing (such as a Turtlebot or Kilobot). This is still an abstract class. It implements abstract methods that are properties of the physical system, such as the communication range (comm_criteria()) and movement restrictions (move()). Gridsim include the GridRobot as a simple robot platform. You can also create your down, as in the YourRobot above.

Second, you create a subclass of your new class for implementing specific algorithms or code on your new robot platform. Here you will implement message handling (receive_msg() and optionally msg_received()) and onboard code (init() and loop()). You can have multiple subclasses of your platform to run different code on the same platform, such as RandomRobot (created below as an example) and AnotherAlgorithm.

Custom robot example

Below is an example of the structure described above to create a simple robot that bounces around the arena.

First, we create , a robot with a circular communication radius of 5 grid cells that can move in the cardinal directions to any of four cells surrounding it. This robot is already provided in the library as GridRobot; you need not re-implement this robot platform if it meets your needs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
from typing import Tuple

from .robot import Robot
# If you are building your own Robot class, you would instead use:
# from gridsim import Robot


class GridRobot(Robot):
    STAY = 'stay'
    UP = 'up'
    DOWN = 'down'
    LEFT = 'left'
    RIGHT = 'right'
    DIRS = [STAY, UP, DOWN, LEFT, RIGHT]

    def __init__(self, x: int, y: int, comm_range: float = 5):
        """
        Create a robot that moves along the cardinal directions. Optionally, you can specify a
        communication range for the robots.

        Parameters
        ----------
        x : int
            Starting x position (grid cell) of the robot
        y : int
            Starting y position (grid cell) of the robot
        comm_range : float, optional
            Communication radius (in grid cells) of the robot, by default 5
        """
        # Run all of the initialization for the default Robot class, including
        # setting the starting position
        super().__init__(x, y)

        self._comm_range = comm_range
        # Start with the robot stationary
        self._move_cmd = GridRobot.STAY

    def set_direction(self, dir: str):
        """
        Helper function to set the direction the robot will move. Note that this will persist (the
        robot will keep moving) until the direction is changed.

        Parameters
        ----------
        dir : int
            Direction to move, one of ``GridRobot.UP``, ``GridRobot.DOWN``, ``GridRobot.LEFT``,
            ``GridRobot.RIGHT``, or ``GridRobot.STAY``

        Raises
        ------
        ValueError
            If given direction is not one of `GridRobot.UP``, ``GridRobot.DOWN``,
            ``GridRobot.LEFT``, ``GridRobot.RIGHT``, or ``GridRobot.STAY``
        """
        if dir in GridRobot.DIRS:
            self._move_cmd = dir
        else:
            raise ValueError('Invalid movement direction "{}"'.format(dir))

    def move(self) -> Tuple[int, int]:
        """
        Determine the cell the Robot will move to, based on the direction set in by
        :meth:`~gridsim.grid_robot.GridRobot.set_motors`.

        Returns
        -------
        Tuple[int, int]
            (x,y) grid cell the robot will move to, if possible/allowed
        """
        x, y = self.get_pos()
        if self._move_cmd == GridRobot.UP:
            y -= 1
        elif self._move_cmd == GridRobot.DOWN:
            y += 1
        elif self._move_cmd == GridRobot.RIGHT:
            x += 1
        elif self._move_cmd == GridRobot.LEFT:
            x -= 1
        # else STAY, which keeps current position
        return x, y

    def comm_criteria(self, dist: int) -> bool:
        """
        Robots can communicate if their Euclidean distance is <= the radius specified at
        initialization (by default, 5 cells)

        Parameters
        ----------
        dist : int
            Euclidean distance of the other robot with which to communicate

        Returns
        -------
        bool
            Whether distance is <= the communication radius
        """
        return dist <= self._comm_range

With our robot platform in place, we can now implement a Robot that implements whatever code we want the robot to run. In this case, it’s a simple robot that chooses a random movement every 10 ticks. Its color is based on the color it samples at its current location, and whether it has communicated with another robot.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import random

from gridsim.grid_robot import GridRobot
import gridsim as gs


class RandomRobot(GridRobot):
    # Change direction every 10 ticks
    DIR_DURATION = 10

    def init(self):
        self.set_color(255, 0, 0)
        self._msg_sent = False

        # Next tick when Robot will change direction
        self._next_dir_change = self.get_tick()

    def receive_msg(self, msg: gs.Message, dist: float):
        # This robot got a message from another robot
        self._msg_sent = True

    def loop(self):
        # Change direction every DIR_DURATION ticks
        tick = self.get_tick()
        if tick >= self._next_dir_change:
            new_dir = random.choice(GridRobot.DIRS)
            self.set_direction(new_dir)
            self._next_dir_change = tick + RandomRobot.DIR_DURATION

        # Broadcast a test message to any robots nearby
        msg = gs.Message(self.id, {'test': 'hello'})
        self.set_tx_message(msg)

        # Sample the environment at the current location
        c = self.sample()

        # Change color depending on whether messages have been sent or received
        # Robot will be white when it has successfully sent & received a message
        blue = 255 * self._msg_sent
        # self.set_color(255, green, 0)
        self.set_color(255-c[0], 255-c[1], blue)

Notice that the abstraction layers mean that you have to write very little additional code to implement a new algorithm for your robot.