Skip to content

Classes

What a class is

A class is a blueprint. Think of a class as the plan for a house: it lists the number of floors, the number of rooms, and so on.

From that plan you build an object. One class can produce many objects, each with its own details.

# create class definition
class House:
    # default attributes of a House
    number_of_floors = 2
    number_of_rooms = 3

# create a House object
my_house = House()

print(my_house.number_of_floors)   # 2
print(my_house.number_of_rooms)    # 3

my_taller_house = House()
my_taller_house.number_of_floors = 4
print(my_taller_house.number_of_floors)   # 4

The values stored in an object are its attributes. Read or change one by putting a . after the object name.

Changing my_taller_house does not affect my_house. They are separate objects.

Indentation matters here too

Everything indented under class House: belongs to the class.

Class methods

A class can contain functions. A function inside a class is called a method. A method can read and change the attributes of its own object.

class Room:
    room_width = 0.0
    room_length = 0.0

    def calculate_area(self):
        return self.room_width * self.room_length

my_room = Room()
my_room.room_length = 5.5
my_room.room_width = 6.0

print(my_room.calculate_area())   # 33.0

Call a method the same way you read an attribute: a . after the object name, then the method name with parentheses.

The self keyword

self is the first argument of every method. It refers to the specific object the method was called on, so self.room_width means "the width of this room" and not the width of every room.

You never pass self in yourself. Python does it for you when you write my_room.calculate_area().

A class can have as many methods as you need.

Where you already see this

MARLIN is built from classes. Robot, Thruster, Servo, and IMU are all classes:

from marlin import Robot, Thruster

robot = Robot()                          # create a Robot object
robot.configure(effectors={"M1": Thruster})
robot.thruster("M1").set_duty(30)        # create a Thruster object, then call a method on it
print(robot.battery_voltage)             # read an attribute

robot is an object. configure() and run() are methods. battery_voltage and running are attributes.

How a class becomes real hardware

Thruster is the blueprint. Plugging a thruster into port M1 and naming that port in configure() is what turns the blueprint into one object that controls one physical thruster.

flowchart LR
    cls["Thruster<br/>the class, a blueprint"]
    conf["You declare port M1 as a Thruster<br/>inside configure()"]
    obj["The Thruster object for M1<br/>created by configure()"]
    use["You fetch that object<br/>and call set_duty(30) on it"]
    hw["The thruster plugged into<br/>socket M1 on the robot<br/>spins at 30%"]

    cls --> conf --> obj
    obj --> use --> hw

You never write Thruster() yourself. configure() builds the objects, and robot.thruster("M1") hands you the one for that port.

A robot with everything plugged in

A robot with two thrusters, a servo, and an IMU needs one line of configuration per socket, and each line produces one object:

flowchart LR
    subgraph hardware["The physical robot"]
        direction TB
        pM1["socket M1<br/>thruster"]
        pM8["socket M8<br/>thruster"]
        pM2["socket M2<br/>servo"]
        pS1["socket S1<br/>IMU"]
    end

    subgraph code["Your Python program"]
        direction TB
        oM1["a Thruster object<br/>robot.thruster(M1)"]
        oM8["a Thruster object<br/>robot.thruster(M8)"]
        oM2["a Servo object<br/>robot.servo(M2)"]
        oS1["an IMU object<br/>robot.imu(S1)"]
    end

    oM1 -->|"set_duty(30)"| pM1
    oM8 -->|"set_duty(30)"| pM8
    oM2 -->|"angle(90)"| pM2
    pS1 -->|"read()"| oS1

In code the port name is a piece of text, so it needs quotes: robot.thruster("M1").

robot.configure(
    sensors={"S1": IMU},
    effectors={"M1": Thruster, "M8": Thruster, "M2": Servo},
)

Two things worth noticing:

  • M1 and M8 are both built from the same Thruster class, but they are two separate objects. set_duty() on one does not move the other, exactly like my_house and my_taller_house above.
  • Effectors point outwards and sensors point inwards. You call methods on an effector to make it move. You read from a sensor with read().

The port name in the code must match the socket the hardware is plugged into. If they disagree, MARLIN stops your program during configure() rather than letting the robot move the wrong part. See Hardware configuration.

Going further

Classes are a part of Object Oriented Programming (OOP). There is much more to OOP in Python. This page covers only the basics you need for your robot.

OOP is used in most modern languages, so it is worth researching further once you are comfortable with what is here.

Full documentation: https://docs.python.org/3/tutorial/classes.html

You now have the Python you need. Continue with the MARLIN API guide, starting at Program structure.