Python как инструмент разработки роботизированных систем

Раздел: Прикладное программирование -> Применение Python

Применение Python для управления роботами

Python широко используется в робототехнике благодаря простоте синтаксиса, большому количеству библиотек и активному сообществу. Рассмотрим основные подходы к программированию роботов на Python: от прямого управления GPIO на одноплатных компьютерах до использования фреймворка ROS.

Как управлять моторами и датчиками через GPIO на Raspberry Pi?

Наиболее распространённый сценарий для простых колёсных роботов или манипуляторов - использование библиотеки RPi.GPIO для прямого управления выводами Raspberry Pi. Ниже приведён пример управления двигателем постоянного тока через драйвер L298N.

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)

# Пины для драйвера L298N
IN1 = 17
IN2 = 27
ENA = 18

GPIO.setup([IN1, IN2, ENA], GPIO.OUT)

pwm = GPIO.PWM(ENA, 100)  # частота 100 Гц
pwm.start(50)  # 50% заполнения

# Вращение вперёд
GPIO.output(IN1, GPIO.HIGH)
GPIO.output(IN2, GPIO.LOW)
time.sleep(2)

# Остановка
GPIO.output(IN1, GPIO.LOW)
GPIO.output(IN2, GPIO.LOW)
pwm.stop()
GPIO.cleanup()

Python язык программирования роботов (python для программирования роботов)

Пояснение: сначала инициализируется режим нумерации пинов. Затем пины настраиваются как выходы. Для управления скоростью используется ШИМ (PWM) на выводе ENA. Комбинация сигналов на IN1/IN2 задаёт направление вращения.

Типичная ошибка: не вызван GPIO.cleanup() после использования, что может привести к тому, что пины останутся в неопределённом состоянии. Также следует запускать скрипт с правами root (через sudo) для доступа к аппаратным регистрам.

Как реализовать программу для микроконтроллера на MicroPython?

Для недорогих контроллеров (ESP32, STM32) используется MicroPython. Рассмотрим пример мигания светодиодом и чтения данных с ультразвукового дальномера HC-SR04.

from machine import Pin, time_pulse_us
import time

led = Pin(2, Pin.OUT)
trig = Pin(5, Pin.OUT)
echo = Pin(18, Pin.IN)

while True:
    led.value(not led.value())  # переключение светодиода
    
    # Генерация импульса для HC-SR04
    trig.value(0)
    time.sleep_us(2)
    trig.value(1)
    time.sleep_us(10)
    trig.value(0)
    
    duration = time_pulse_us(echo, 1)  # время отклика в микросекундах
    distance = duration * 0.034 / 2  # пересчёт в сантиметры
    print('Distance:', distance, 'cm')
    time.sleep(1)

MicroPython предоставляет объектно-ориентированный интерфейс к пинам. machine.Pin управляет цифровыми выводами, а time_pulse_us измеряет длину импульса. Важно: ультразвуковой датчик требует точного измерения времени, поэтому используется аппаратный счётчик.

Проблема: на некоторых платах ESP32 пульсация питания может вызывать ложные срабатывания. Решение - добавить конденсатор 100 мкФ между Vcc и GND датчика. Также не стоит забывать о подтягивающих резисторах для линий данных.

Как создать распределённую систему управления с помощью ROS и rospy?

Для сложных роботов (например, мобильных платформ с лидарами и камерами) используется Robot Operating System (ROS). Python-клиент rospy позволяет создавать узлы (nodes), публиковать и подписываться на топики. Ниже простейший узел, который управляет колёсами по команде.

#!/usr/bin/env python3
import rospy
from geometry_msgs.msg import Twist
from std_msgs.msg import String

def callback(data):
    # Обработка входящих команд
    cmd = data.data
    move_cmd = Twist()
    if cmd == 'forward':
        move_cmd.linear.x = 0.5
        move_cmd.angular.z = 0.0
    elif cmd == 'left':
        move_cmd.linear.x = 0.0
        move_cmd.angular.z = 0.5
    # ... другие команды
    pub.publish(move_cmd)

rospy.init_node('cmd_vel_mux')
sub = rospy.Subscriber('cmd', String, callback)
pub = rospy.Publisher('cmd_vel', Twist, queue_size=10)
rospy.spin()

Узел подписывается на топик cmd, получает строку с командой, формирует сообщение типа Twist и публикует в топик cmd_vel, который затем обрабатывается драйвером колёс. ROS обеспечивает модульность и возможность запуска узлов на разных компьютерах.

Типичная ошибка: забыть запустить rospy.spin() или rospy.Rate для цикла. Это приведёт к тому, что узел завершится сразу после инициализации. Также важно синхронизировать частоту публикации с реальной скоростью робота.

Расширенные примеры программирования роботов на Python

1. Дистанционное управление по Bluetooth с Android

На Raspberry Pi через Bluetooth SPP (серийный порт) можно принимать команды со смартфона. Устанавливается библиотека pybluez.

Пример
import bluetooth
import RPi.GPIO as GPIO

server_sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
server_sock.bind(("", bluetooth.PORT_ANY))
server_sock.listen(1)

port = server_sock.getsockname()[1]
bluetooth.advertise_service(server_sock, "RobotCtrl", service_id=...,
                            service_classes=[bluetooth.SERIAL_PORT_CLASS])

client_sock, client_info = server_sock.accept()
try:
    while True:
        data = client_sock.recv(1024)
        if data:
            # Парсинг команды (например, 'w','a','s','d')
            command = data.decode().strip()
            print("Received:", command)
            # Далее вызов функции управления моторами
except KeyboardInterrupt:
    client_sock.close()
    server_sock.close()
    GPIO.cleanup()
(При подключении со смартфона отправляется символ 'w' – робот едет вперёд)

Для работы требуется установить pybluez (sudo pip3 install pybluez). На Android используется приложение Serial Bluetooth Terminal. Важно: на Raspberry Pi Bluetooth может конфликтовать с Wi-Fi, рекомендуется использовать внешний USB-адаптер.

2. Обработка видеопотока с камеры для отслеживания объектов

Библиотека OpenCV позволяет анализировать изображение в реальном времени. Пример поиска красного предмета и вычисления его центра.

Пример
import cv2
import numpy as np

cap = cv2.VideoCapture(0)
while True:
    ret, frame = cap.read()
    if not ret:
        break
    # Преобразование в HSV
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    # Диапазон красного цвета (два интервала из-за особенностей HSV)
    lower_red1 = np.array([0, 70, 50])
    upper_red1 = np.array([10, 255, 255])
    lower_red2 = np.array([170, 70, 50])
    upper_red2 = np.array([180, 255, 255])
    mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
    mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
    mask = cv2.bitwise_or(mask1, mask2)
    # Нахождение контуров
    contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if contours:
        largest = max(contours, key=cv2.contourArea)
        M = cv2.moments(largest)
        if M['m00'] != 0:
            cx = int(M['m10'] / M['m00'])
            cy = int(M['m01'] / M['m00'])
            cv2.circle(frame, (cx, cy), 5, (0,255,0), -1)
            # Координаты можно передать в управление моторами
    cv2.imshow('Frame', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
cap.release()
cv2.destroyAllWindows()
(В окне выводится видео с выделенным центром красного объекта)

Для OpenCV под Raspberry Pi требуется ускорение (использовать cv2.CAP_V4L2 или установить opencv-contrib-python). Проблема: высокая загрузка CPU – рекомендуется понижать разрешение или использовать аппаратный H.264 декодер.

3. Планирование пути методом A* для мобильного робота

Алгоритм A* находит кратчайший путь на сетке с учётом препятствий. Реализация на чистом Python без сторонних библиотек.

Пример
import heapq

def heuristic(a, b):
    return abs(a[0]-b[0]) + abs(a[1]-b[1])

def astar(grid, start, goal):
    rows, cols = len(grid), len(grid[0])
    open_set = []
    heapq.heappush(open_set, (0, start))
    came_from = {}
    g_score = {start: 0}
    f_score = {start: heuristic(start, goal)}
    
    while open_set:
        _, current = heapq.heappop(open_set)
        if current == goal:
            path = []
            while current in came_from:
                path.append(current)
                current = came_from[current]
            path.reverse()
            return path
        
        for dx, dy in [(0,1),(0,-1),(1,0),(-1,0)]:
            neighbor = (current[0]+dx, current[1]+dy)
            if 0 <= neighbor[0] < rows and 0 <= neighbor[1] < cols and grid[neighbor[0]][neighbor[1]] == 0:
                tentative_g = g_score[current] + 1
                if tentative_g < g_score.get(neighbor, float('inf')):
                    came_from[neighbor] = current
                    g_score[neighbor] = tentative_g
                    f_score[neighbor] = tentative_g + heuristic(neighbor, goal)
                    heapq.heappush(open_set, (f_score[neighbor], neighbor))
    return None

grid = [
 [0,0,0,0,0],
 [0,1,1,1,0],
 [0,0,0,1,0],
 [0,0,0,0,0]
]
path = astar(grid, (0,0), (3,4))
print(path)
[(0,0), (1,0), (2,0), (2,1), (2,2), (3,2), (3,3), (3,4)]

Результат – список координат ячеек. Для интеграции с ROS можно упаковать в сервис или actionlib. Проблема: на больших картах памяти может не хватить – используется бинарная куча и компактное хранение графа.

4. Управление сервоприводами через i2c (PCA9685)

Модуль PCA9685 позволяет управлять 16 сервоприводами по шине I2C. Библиотека Adafruit_CircuitPython_ServoKit упрощает задачу.

Пример
from adafruit_servokit import ServoKit
kit = ServoKit(channels=16)
kit.servo[0].angle = 90   # серво на канале 0 поворот на 90 градусов
kit.servo[1].angle = 45
(Сервоприводы плавно перемещаются в заданные позиции)

Для установки: sudo pip3 install adafruit-circuitpython-servokit. Требуется активировать I2C на Raspberry Pi (raspi-config). Типичная ошибка: напряжение питания сервоприводов не должно подаваться от пинов GPIO – используется внешний источник 5В.

5. Интеграция Python с ROS2 (rclpy)

ROS2 (Robot Operating System 2) использует Python-клиент rclpy. Пример простого издателя и подписчика.

Пример
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class Talker(Node):
    def __init__(self):
        super().__init__('talker')
        self.pub = self.create_publisher(String, 'chatter', 10)
        timer_period = 0.5
        self.timer = self.create_timer(timer_period, self.timer_callback)
        self.counter = 0

    def timer_callback(self):
        msg = String()
        msg.data = f'Hello {self.counter}'
        self.pub.publish(msg)
        self.get_logger().info('Publishing: "%s"' % msg.data)
        self.counter += 1

def main(args=None):
    rclpy.init(args=args)
    node = Talker()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()
[INFO] [talker]: Publishing: "Hello 0"
[INFO] [talker]: Publishing: "Hello 1" ...

ROS2 обеспечивает лучшую поддержку реального времени и мультиплатформенность по сравнению с ROS1. Необходимо установить ROS2 (Humble или Rolling) и colcon для сборки пакетов.

Python для программирования роботов - comments

En
Python язык программирования роботов (python)