Effectors¶
Effector objects are created by Robot.configure(). Calling an effector method
records a setpoint; MARLIN keeps sending that setpoint to the robot until you
change it, so a value you set once stays applied even when your loop does not
repeat the call.
Every effector method returns None. You call them for their effect on the
robot, not for a value.
Effector¶
Base class for effectors. It is public for extension and advanced use, but
you normally configure Thruster or Servo.
Attributes¶
| Attribute | Type | Meaning |
|---|---|---|
port |
str |
The port this effector was declared on, such as "M1" |
type |
str |
The lowercase type name, "thruster" or "servo" |
Thruster¶
set_duty¶
Input: duty, a number from -100 to 100. Output: none.
duty |
Result |
|---|---|
100 |
Full forward |
30 |
Forward, roughly a third of full power |
0 |
Stopped |
-30 |
Reverse, roughly a third of full power |
-100 |
Full reverse |
Positive is forward. MARLIN handles the wiring direction for you, so you do not need to negate anything to make a thruster drive forwards.
Duty is open-loop. Open-loop means MARLIN sends the power level you ask for. MARLIN does not measure the real speed. MARLIN does not correct the speed. Two thrusters set to the same duty can turn at different speeds.
The library rounds the value to an integer. A value past either end is pinned to that end rather than raising, so mixing two stick readings is safe to write directly.
robot.thruster("M1").set_duty(40) # forward at 40%
robot.thruster("M1").set_duty(-40) # reverse at 40%
robot.thruster("M1").set_duty(0) # stop
robot.thruster("M1").set_duty(140) # runs at 100, the same as full forward
The one value it will not take is nan, which no clamp can place. The setpoint
is left as it was and MARLIN prints a line naming the port and the setter, once
per setter, so a 50 Hz loop cannot bury the console.
Worked example: mirrored thrusters¶
Two thrusters mounted facing each other need opposite duties to push the robot the same way:
def drive_forward(robot, power):
robot.thruster("M1").set_duty(power)
robot.thruster("M8").set_duty(-power)
def autonomous(robot):
while robot.running:
drive_forward(robot, 30)
Servo¶
Servo drives a positional hobby servo: a servo with a limited range of travel,
0 to 180 degrees.
Use angle() for normal work. micros() is a manual override that sets the
underlying pulse directly.
angle¶
Input: deg, a number from 0 to 180. Output: none.
Sets servo position in degrees. An angle past either end is pinned to that end.
robot.servo("M2").angle(0) # one end of travel
robot.servo("M2").angle(90) # centre
robot.servo("M2").angle(180) # the other end
robot.servo("M2").angle(200) # goes to 180, the end of travel
micros¶
Advanced
Use angle() unless it does not reach the positions you need.
Input: us, a whole number of microseconds from 500 to 2500.
Output: none.
Sets the raw pulse width. A servo is told what to do by a pulse width: the
length, in microseconds, of an electrical pulse repeated many times a second.
angle() does this conversion for you, so reach for micros() only when your
servo does not reach both ends of its travel at 0 and 180 degrees.
A pulse past either end is pinned to that end.
Worked example: sweeping an arm between two positions¶
Press A to raise the arm, press it again to lower it. raised remembers
which position the arm is in, because the servo cannot tell you.
def driver(robot):
previous = False
raised = False
while robot.running:
current = robot.controller.button("A").is_down
if current and not previous: # a new press, not a held button
if raised:
raised = False
robot.servo("M2").angle(0)
else:
raised = True
robot.servo("M2").angle(120)
previous = current
Errors shared by effectors¶
- Commands on an unattached, directly constructed effector raise
RuntimeError. - Typed robot accessors raise
TypeErrorwhen the port has the wrong effector type. For example,robot.thruster("M1")raisesTypeErrorif portM1is configured as aServo.