4. Recipes
In addition to the examples provided for each component/device in the API reference section of this documentation, the following recipes demonstrate some of the more advanced capabilities of the pi-top Python SDK. In particular, these recipes focus on practical use-cases that make use of multiple components/devices within the pi-top Python SDK.
Be sure to check out each component/device separately for simple examples of how to use them.
4.2. Robotics Kit: DIY Rover
from threading import Thread
from time import sleep
from pitop import BrakingType, EncoderMotor, ForwardDirection
# Setup the motors for the rover configuration
motor_left = EncoderMotor("M3", ForwardDirection.CLOCKWISE)
motor_right = EncoderMotor("M0", ForwardDirection.COUNTER_CLOCKWISE)
motor_left.braking_type = BrakingType.COAST
motor_right.braking_type = BrakingType.COAST
# Define some functions for easily controlling the rover
def drive(target_rpm: float):
print("Start driving at target", target_rpm, "rpm...")
motor_left.set_target_rpm(target_rpm)
motor_right.set_target_rpm(target_rpm)
def stop_rover():
print("Stopping rover...")
motor_left.stop()
motor_right.stop()
def turn_left(rotation_speed: float):
print("Turning left...")
motor_left.stop()
motor_right.set_target_rpm(rotation_speed)
def turn_right(rotation_speed: float):
print("Turning right...")
motor_right.stop()
motor_left.set_target_rpm(rotation_speed)
# Start a thread to monitor the rover
def monitor_rover():
while True:
print(
"> Rover motor RPM's (L,R):",
round(motor_left.current_rpm, 2),
round(motor_right.current_rpm, 2),
)
sleep(1)
monitor_thread = Thread(target=monitor_rover, daemon=True)
monitor_thread.start()
# Go!
rpm_speed = 100
for _ in range(4):
drive(rpm_speed)
sleep(5)
turn_left(rpm_speed)
sleep(5)
stop_rover()
4.3. Robotics Kit: Robot - Moving Randomly
from random import randint
from time import sleep
from pitop import Pitop
from pitop.robotics.drive_controller import DriveController
# Create a basic robot
robot = Pitop()
drive = DriveController(left_motor_port="M3", right_motor_port="M0")
robot.add_component(drive)
# Use miniscreen display
robot.miniscreen.display_multiline_text("hey there!")
def random_speed_factor():
# 0.01 - 1, 0.01 resolution
return randint(1, 100) / 100
def random_sleep():
# 0.5 - 2, 0.5 resolution
return randint(1, 4) / 2
# Move around randomly
robot.drive.forward(speed_factor=random_speed_factor())
sleep(random_sleep())
robot.drive.left(speed_factor=random_speed_factor())
sleep(random_sleep())
robot.drive.backward(speed_factor=random_speed_factor())
sleep(random_sleep())
robot.drive.right(speed_factor=random_speed_factor())
sleep(random_sleep())
4.4. Robotics Kit: Robot - Line Detection
from signal import pause
from pitop import Camera, DriveController, Pitop
from pitop.processing.algorithms.line_detect import process_frame_for_line
# Assemble a robot
robot = Pitop()
robot.add_component(DriveController(left_motor_port="M3", right_motor_port="M0"))
robot.add_component(Camera())
# Set up logic based on line detection
def drive_based_on_frame(frame):
processed_frame = process_frame_for_line(frame)
if processed_frame.line_center is None:
print("Line is lost!", end="\r")
robot.drive.stop()
else:
print(f"Target angle: {processed_frame.angle:.2f} deg ", end="\r")
robot.drive.forward(0.25, hold=True)
robot.drive.target_lock_drive_angle(processed_frame.angle)
robot.miniscreen.display_image(processed_frame.robot_view)
# On each camera frame, detect a line
robot.camera.on_frame = drive_based_on_frame
pause()
4.5. Displaying camera stream in pi-top [4]’s miniscreen
from pitop import Camera, Pitop
camera = Camera()
pitop = Pitop()
camera.on_frame = pitop.miniscreen.display_image
4.6. Robotics Kit: Robot - Control using Bluedot
Note
BlueDot is a Python library that allows you to control Raspberry Pi projects remotely. This example demonstrates a way to control a robot with a virtual joystick.
from signal import pause
from threading import Lock
from bluedot import BlueDot
from pitop import DriveController
bd = BlueDot()
bd.color = "#00B2A2"
lock = Lock()
drive = DriveController(left_motor_port="M3", right_motor_port="M0")
def move(pos):
if lock.locked():
return
if any(
[
pos.angle > 0 and pos.angle < 20,
pos.angle < 0 and pos.angle > -20,
]
):
drive.forward(pos.distance, hold=True)
elif pos.angle > 0 and 20 <= pos.angle <= 160:
turn_radius = 0 if 70 < pos.angle < 110 else pos.distance
speed_factor = -pos.distance if pos.angle > 110 else pos.distance
drive.right(speed_factor, turn_radius)
elif pos.angle < 0 and -160 <= pos.angle <= -20:
turn_radius = 0 if -110 < pos.angle < -70 else pos.distance
speed_factor = -pos.distance if pos.angle < -110 else pos.distance
drive.left(speed_factor, turn_radius)
elif any(
[
pos.angle > 0 and pos.angle > 160,
pos.angle < 0 and pos.angle < -160,
]
):
drive.backward(pos.distance, hold=True)
def stop(pos):
lock.acquire()
drive.stop()
def start(pos):
if lock.locked():
lock.release()
move(pos)
bd.when_pressed = start
bd.when_moved = move
bd.when_released = stop
pause()
4.7. Using the pi-topPULSE’s LED matrix to show the battery level
from time import sleep
from pitop import Pitop
from pitop.pulse import ledmatrix
def draw_battery_outline(): # Draw the naked battery
for y in range(0, 6):
ledmatrix.set_pixel(1, y, 64, 64, 255)
ledmatrix.set_pixel(5, y, 64, 64, 255)
for x in range(2, 5):
ledmatrix.set_pixel(x, 0, 64, 64, 255)
ledmatrix.set_pixel(x, 6, 192, 192, 192)
ledmatrix.show()
def update_battery_state(charging_state, capacity):
r = 0
g = 0
b = 0
if charging_state == 0:
if capacity < 11:
r = 255
else:
g = 255
elif charging_state == 1:
r = 255
g = 225
cap = int(capacity / 20) + 1
if cap < 0:
cap = 0
if cap > 5:
cap = 5
if cap > 0:
for y in range(1, cap + 1):
ledmatrix.set_pixel(2, y, r, g, b)
ledmatrix.set_pixel(3, y, r, g, b)
ledmatrix.set_pixel(4, y, r, g, b)
if cap == 0:
cap = 1
if cap < 6:
if (capacity < 50) and (charging_state == 0):
# blinking warning
for i in range(1, 3):
for y in range(cap + 1, 6):
ledmatrix.set_pixel(2, y, 0, 0, 0)
ledmatrix.set_pixel(3, y, 0, 0, 0)
ledmatrix.set_pixel(4, y, 0, 0, 0)
ledmatrix.show()
sleep(0.4)
for y in range(cap + 1, 6):
ledmatrix.set_pixel(2, y, 255, 0, 0)
ledmatrix.set_pixel(3, y, 255, 0, 0)
ledmatrix.set_pixel(4, y, 255, 0, 0)
ledmatrix.show()
sleep(0.4)
else:
for y in range(cap + 1, 6):
ledmatrix.set_pixel(2, y, 0, 0, 0)
ledmatrix.set_pixel(3, y, 0, 0, 0)
ledmatrix.set_pixel(4, y, 0, 0, 0)
ledmatrix.show()
sleep(5)
return 0
def main():
ledmatrix.rotation(0)
ledmatrix.clear() # Clear the display
draw_battery_outline() # Draw the battery outline
battery = Pitop().battery
while True:
try:
charging_state, capacity, _, _ = battery.get_full_state()
update_battery_state(charging_state, capacity) # Fill battery with capacity
except Exception as e:
print("Error getting battery info: " + str(e))
if __name__ == "__main__":
main()
4.8. Choose a pi-top [4] miniscreen startup animation
Note
This code makes use of the GIPHY SDK. Follow the instructions here to find out how to apply for an API Key to use with this project.
Replace <MY GIPHY KEY> with the key provided (keep the quotes).
You can change the type of images that you get by changing SEARCH_TERM = “Monochrome” to whatever you want.
import json
from configparser import ConfigParser
from os import geteuid
from random import randint
from signal import pause
from sys import exit
from time import sleep
from urllib.parse import urlencode
from urllib.request import urlopen
from PIL import Image
from requests.models import PreparedRequest
from pitop.miniscreen import Miniscreen
def is_root():
return geteuid() == 0
if not is_root():
print("Admin access required - please run this script with 'sudo'.")
exit()
# Define Giphy parameters
SEARCH_LIMIT = 10
SEARCH_TERM = "Monochrome"
CONFIG_FILE_PATH = "/etc/pt-miniscreen/settings.ini"
STARTUP_GIF_PATH = "/home/pi/miniscreen-startup.gif"
API_KEY = "<MY GIPHY KEY>"
# Define global variables
gif = None
miniscreen = Miniscreen()
req = PreparedRequest()
req.prepare_url(
"http://api.giphy.com/v1/gifs/search",
urlencode({"q": SEARCH_TERM, "api_key": API_KEY, "limit": f"{SEARCH_LIMIT}"}),
)
def display_instructions_dialog():
miniscreen.select_button.when_pressed = play_random_gif
miniscreen.cancel_button.when_pressed = None
miniscreen.display_multiline_text(
"Press SELECT to load a random GIF!", font_size=18
)
def display_user_action_select_dialog():
miniscreen.select_button.when_pressed = save_gif_as_startup
miniscreen.cancel_button.when_pressed = play_random_gif
miniscreen.display_multiline_text(
"SELECT: save GIF as default startup animation. CANCEL: load new GIF",
font_size=12,
)
def display_loading_dialog():
miniscreen.select_button.when_pressed = None
miniscreen.cancel_button.when_pressed = display_instructions_dialog
miniscreen.display_multiline_text("Loading random GIF...", font_size=18)
def display_saving_dialog():
miniscreen.select_button.when_pressed = None
miniscreen.cancel_button.when_pressed = None
miniscreen.display_multiline_text(
"GIF saved as default startup animation!", font_size=18
)
# Saving is fast, so we need to wait a short while for the message to be seen on the display
sleep(1)
def play_random_gif():
global gif
# Show "Loading..." while processing for a GIF
display_loading_dialog()
# Get GIF data from Giphy
with urlopen(req.url) as response:
data = json.loads(response.read())
# Extract random GIF URL from JSON response
gif_url = data["data"][randint(0, SEARCH_LIMIT - 1)]["images"]["fixed_height"][
"url"
]
# Load GIF from URL
gif = Image.open(urlopen(gif_url))
# Play one loop of GIF animation
miniscreen.play_animated_image(gif)
# Ask user if they want to save it
display_user_action_select_dialog()
def save_gif_as_startup():
# Display "saving" dialog
display_saving_dialog()
# Save file to home directory
gif.save(STARTUP_GIF_PATH, save_all=True)
config = ConfigParser()
cfg_section = "Bootsplash"
if not config.has_section(cfg_section):
config.add_section(cfg_section)
config.set(cfg_section, "Path", STARTUP_GIF_PATH)
with open(CONFIG_FILE_PATH, "w") as f:
config.write(f)
# Go back to the start
display_instructions_dialog()
# Display initial dialog
display_instructions_dialog()
# Wait indefinitely for user input
pause()