Advanced#
This chapter goes through some advanced botafar features not seen in Get started and Basics as well as more advanced hardware examples than in Arduino tutorial and Raspberry Pi tutorial.
Advanced LED examples#
Toggling LED#
An example that shows how to toggle an LED
from gpiozero import LED
import botafar
b = botafar.Button("L", alt="SPACE")
led = LED(17)
b.on_press(led.toggle)
botafar.on_prepare(led.off) # Turn off between controls
botafar.run()
Advanced servo examples#
These servo examples could be used for example steering a RC car. Different movement styles would provide a different driving experience.
These examples leverage Event.is_active
property. This has a value of True
or False
depending on is the event the latest one or not.
Immediate servo#
Servo example with a custom class, immediate movement to the target value.
from gpiozero import Servo
from gpiozero.pins.pigpio import PiGPIOFactory # Optional, removes servo stutter!
import botafar
SERVO_GPIO_PIN = 17
SERVO_VALUES = {
"on_left": 1,
"on_up_left": 0.5,
"on_up": 0,
"on_center": 0,
"on_up_right": -0.5,
"on_right": -1,
}
j = botafar.Joystick("W", "A", "S", "D", alt=["UP", "LEFT", "DOWN", "RIGHT"], diagonals=True)
class ImmediateServo:
def __init__(self):
self.servo = Servo(
SERVO_GPIO_PIN,
pin_factory=PiGPIOFactory(),
min_pulse_width=0.544 / 1000, # Adjust if needed
max_pulse_width=2.4 / 1000, # Adjust if needed
)
@j.on_any
def move_servo(self, event):
servo_value = SERVO_VALUES.get(event.name)
if servo_value is not None:
botafar.print(f"servo value {servo_value}")
self.servo.value = servo_value
ImmediateServo()
botafar.run()
Smooth servo#
Servo example with a custom class, smooth movement to the target value.
Adjust SERVO_STEPS
and SERVO_UPDATE_FREQ
to alter movement speed and smoothness.
from gpiozero import Servo
from gpiozero.pins.pigpio import PiGPIOFactory # Optional, removes servo stutter!
from time import sleep
from threading import Lock
import botafar
SERVO_GPIO_PIN = 17
SERVO_STEPS = 40
SERVO_UPDATE_FREQ = 25
SERVO_VALUES = {
"on_left": 1,
"on_up_left": 0.5,
"on_up": 0,
"on_center": 0,
"on_up_right": -0.5,
"on_right": -1,
}
j = botafar.Joystick("W", "A", "S", "D", alt=["UP", "LEFT", "DOWN", "RIGHT"], diagonals=True)
class SmoothServo:
def __init__(self):
self.servo = Servo(
SERVO_GPIO_PIN,
pin_factory=PiGPIOFactory(),
min_pulse_width=0.544 / 1000, # Adjust if needed
max_pulse_width=2.4 / 1000, # Adjust if needed
)
self.target_value = 0
self.sleep_amount = 1 / SERVO_UPDATE_FREQ
self.move_amount = 2 / SERVO_STEPS
self.lock = Lock() # Fixes concurrency issues
@j.on_any
def move_servo(self, event):
with self.lock:
target_value = SERVO_VALUES.get(event.name)
if target_value is not None:
botafar.print(f"servo target value {target_value}")
while event.is_active and target_value != self.servo.value:
if target_value < self.servo.value:
self.servo.value = max(target_value, self.servo.value - self.move_amount)
elif target_value > self.servo.value:
self.servo.value = min(target_value, self.servo.value + self.move_amount)
sleep(self.sleep_amount)
SmoothServo()
botafar.run()
Partially smooth servo#
Servo example with a custom class, smooth movement to the target value, immediate return to center.
Adjust SERVO_STEPS
and SERVO_UPDATE_FREQ
to alter movement speed and smoothness.
from gpiozero import Servo
from gpiozero.pins.pigpio import PiGPIOFactory # Optional, removes servo stutter!
from time import sleep
from threading import Lock
import botafar
SERVO_GPIO_PIN = 17
SERVO_STEPS = 40
SERVO_UPDATE_FREQ = 25
SERVO_VALUES = {
"on_left": 1,
"on_up_left": 0.5,
"on_up": 0,
"on_center": 0,
"on_up_right": -0.5,
"on_right": -1,
}
j = botafar.Joystick("W", "A", "S", "D", alt=["UP", "LEFT", "DOWN", "RIGHT"], diagonals=True)
class PartiallySmoothServo:
def __init__(self):
self.servo = Servo(
SERVO_GPIO_PIN,
pin_factory=PiGPIOFactory(),
min_pulse_width=0.544 / 1000, # Adjust if needed
max_pulse_width=2.4 / 1000, # Adjust if needed
)
self.target_value = 0
self.sleep_amount = 1 / SERVO_UPDATE_FREQ
self.move_amount = 2 / SERVO_STEPS
self.lock = Lock() # Fixes concurrency issues
@j.on_any
def move_servo(self, event):
with self.lock:
target_value = SERVO_VALUES.get(event.name)
if target_value is not None:
botafar.print(f"servo target value {target_value}")
while event.is_active and target_value != self.servo.value:
if target_value == 0:
self.servo.value = target_value
elif target_value < self.servo.value:
self.servo.value = max(target_value, self.servo.value - self.move_amount)
elif target_value > self.servo.value:
self.servo.value = min(target_value, self.servo.value + self.move_amount)
sleep(self.sleep_amount)
PartiallySmoothServo()
botafar.run()
Other examples#
Not spammable relay#
Example how to connect a relay to gpiozero OutputDevice, and prevent players from spamming it too fast.
from gpiozero import OutputDevice
import botafar
from time import sleep
b = botafar.Button("SPACE")
RELAY_GPIO_PIN = 17
RELAY_TIME_ON = 0.1
RELAY_TIME_OFF = 2
class NotSpammableRelay:
def __init__(self):
self.relay = OutputDevice(RELAY_GPIO_PIN, active_high=True, initial_value=False)
self.cycling = False
@b.on_press
def cycle_relay(self):
if self.cycling:
botafar.print("Spamming relay too fast, skipping")
return
self.cycling = True
self.relay.on()
sleep(RELAY_TIME_ON)
self.relay.off()
sleep(RELAY_TIME_OFF)
self.cycling = False
NotSpammableRelay()
botafar.run()
Project blueprints#
Here is code for some typical bot types, you can use as starting points for your projects.
Tank#
Basic tank#
Example how to create a tank controllable by two instances of gpiozero Motor and botafar.Joystick. This example works if you have two independently controllable wheels, left and right one.
Tank wiring example from gpiozero Robot tutorial using SN754410 motor driver, image licence.
from gpiozero import Motor
import botafar
LEFT_MOTOR_FORWARD_GPIO_PIN = 4
LEFT_MOTOR_BACKWARD_GPIO_PIN = 14
RIGHT_MOTOR_FORWARD_GPIO_PIN = 17
RIGHT_MOTOR_BACKWARD_GPIO_PIN = 18
MOTOR_VALUES = {
"on_center": ( 0.0, 0.0),
"on_up": ( 1.0, 1.0),
"on_up_left": ( 0.5, 1.0),
"on_left": (-0.5, 0.5),
"on_down_left": (-0.5,-1.0),
"on_down": (-1.0,-1.0),
"on_down_right": (-1.0,-0.5),
"on_right": ( 0.5,-0.5),
"on_up_right": ( 1.0, 0.5),
}
j = botafar.Joystick("W", "A", "S", "D", alt=["UP", "LEFT", "DOWN", "RIGHT"], diagonals=True)
left_motor = Motor(
forward=LEFT_MOTOR_FORWARD_GPIO_PIN,
backward=LEFT_MOTOR_BACKWARD_GPIO_PIN,
)
right_motor = Motor(
forward=RIGHT_MOTOR_FORWARD_GPIO_PIN,
backward=RIGHT_MOTOR_BACKWARD_GPIO_PIN,
)
@j.on_any
def drive_tank(event):
left_value, right_value = MOTOR_VALUES[event.name]
left_motor.value = left_value
right_motor.value = right_value
botafar.run()
Smooth tank#
This is a similar example to the previous one, but this changes motor speeds smoothly.
Change MOTOR_STEPS
and MOTOR_UPDATE_FREQ
to change how tank accelerates.
from threading import Lock
from time import sleep
from gpiozero import Motor
import botafar
LEFT_MOTOR_FORWARD_GPIO_PIN = 4
LEFT_MOTOR_BACKWARD_GPIO_PIN = 14
RIGHT_MOTOR_FORWARD_GPIO_PIN = 17
RIGHT_MOTOR_BACKWARD_GPIO_PIN = 18
MOTOR_STEPS = 40 # How many distinct motor speeds
MOTOR_UPDATE_FREQ = 25 # How many speed updates per second
LEFT_MOTOR_VALUES = {
"on_center": 0.0,
"on_up": 1.0,
"on_up_left": 0.5,
"on_left": -0.5,
"on_down_left": -0.5,
"on_down": -1.0,
"on_down_right": -1.0,
"on_right": 0.5,
"on_up_right": 1.0,
}
RIGHT_MOTOR_VALUES = {
"on_center": 0.0,
"on_up": 1.0,
"on_up_left": 1.0,
"on_left": 0.5,
"on_down_left": -1.0,
"on_down": -1.0,
"on_down_right": -0.5,
"on_right": -0.5,
"on_up_right": 0.5,
}
j = botafar.Joystick(
"W", "A", "S", "D", alt=["UP", "LEFT", "DOWN", "RIGHT"], diagonals=True
)
class SmoothMotor:
def __init__(self, forward_pin, backward_pin, values):
self.motor = Motor(
forward=forward_pin,
backward=backward_pin,
)
self.values = values
self.target_value = 0
self.sleep_amount = 1 / MOTOR_UPDATE_FREQ
self.change_amount = 2 / MOTOR_STEPS
self.lock = Lock() # Fixes concurrency issues
@j.on_any
def drive(self, event):
with self.lock:
target_value = self.values[event.name]
botafar.print(f"motor target value {target_value}")
while event.is_active and target_value != self.motor.value:
if target_value < self.motor.value:
self.motor.value = max(
target_value, self.motor.value - self.change_amount
)
elif target_value > self.motor.value:
self.motor.value = min(
target_value, self.motor.value + self.change_amount
)
sleep(self.sleep_amount)
SmoothMotor(
LEFT_MOTOR_FORWARD_GPIO_PIN,
LEFT_MOTOR_BACKWARD_GPIO_PIN,
LEFT_MOTOR_VALUES,
)
SmoothMotor(
RIGHT_MOTOR_FORWARD_GPIO_PIN,
RIGHT_MOTOR_BACKWARD_GPIO_PIN,
RIGHT_MOTOR_VALUES,
)
botafar.run()
RC car#
Example how to create a RC car controllable by gpiozero Servo, gpiozero Motor and botafar.Joystick. Motor sets the wheel spinning speed, servo steers the front wheels.
The servo turning and motor speed changes could modified to work more smoothly by using ideas from Smooth servo and Smooth tank.
from gpiozero import Servo, Motor
from gpiozero.pins.pigpio import PiGPIOFactory # Optional, removes servo stutter!
import botafar
SERVO_GPIO_PIN = 17
MOTOR_FORWARD_GPIO_PIN = 4
MOTOR_BACKWARD_GPIO_PIN = 14
SERVO_VALUES = {
"on_left": 1,
"on_up_left": 0.5,
"on_down_left": 0.5,
"on_up": 0,
"on_down": 0,
"on_center": 0,
"on_up_right": -0.5,
"on_down_right": -0.5,
"on_right": -1,
}
MOTOR_VALUES = {
"on_up": 1,
"on_up_right": 1,
"on_up_left": 1,
"on_right": 1,
"on_left": 1,
"on_center": 0,
"on_down": -1,
"on_down_right": -1,
"on_down_left": -1,
}
j = botafar.Joystick("W", "A", "S", "D", alt=["UP", "LEFT", "DOWN", "RIGHT"], diagonals=True)
# Can be changed to SmoothServo, PartiallySmoothServo
class ImmediateServo:
def __init__(self):
self.servo = Servo(
SERVO_GPIO_PIN,
pin_factory=PiGPIOFactory(),
min_pulse_width=0.544 / 1000, # Adjust if needed
max_pulse_width=2.4 / 1000, # Adjust if needed
)
@j.on_any
def move_servo(self, event):
servo_value = SERVO_VALUES.get(event.name)
if servo_value is not None:
botafar.print(f"servo value {servo_value}")
self.servo.value = servo_value
# Can be changed to SmoothMotor from Smooth tank example
class ImmediateMotor:
def __init__(self):
self.motor = Motor(
forward=MOTOR_FORWARD_GPIO_PIN,
backward=MOTOR_BACKWARD_GPIO_PIN,
)
@j.on_any
def drive(self, event):
motor_value = MOTOR_VALUES.get(event.name)
if motor_value is not None:
botafar.print(f"motor value {motor_value}")
self.motor.value = motor_value
ImmediateServo()
ImmediateMotor()
botafar.run()
Coming soon#
Holding keys, and related topics (using Event)
Detecting who is controlling (using Event)
Owner only controls (2 ways)
Botafar cli