Управление камерой на сервоприводах с помощью Raspberry Pi 2 и фреймворка WebIOPi

Автор: Мясіщев Олександр Анатолійович

Дата публікації: 26 квітня 2016 р. 07:09:19 GMT / Категорія: Розробки кафедри

Управление камерой на сервоприводах с помощью Raspberry Pi 2 и фреймворка WebIOPi
    Необходимо создать систему удаленного управления камерой, входящей в состав мини компьютера Raspberry Pi 2 с помощью сервоприводов, вращающих камеру в двух плоскостях. Для управления сервоприводами используется фреймворк WebIOPi, видео поток с камеры на удаленный браузер будет формировать  mjpg-streamer. Для ознакомления с материалом предполагается, что была прочитана статья. На рисунке 1  показано устройство в сборе.
 
Рис. 1. Фото камеры на сервоприводах с подключенным к ней Raspberri Pi 2
 
       На рисунке 2 показана картинка с камеры для одного из ее положений и кнопки управления сервоприводами(серые) с индикацией положения(синие). В строку ввода Input step вводится шаг поворота сервопривода в градусах.  Рассмотрен самый простой случай - загружены два окна браузера Chrome. Первое окно подключено к web - серверу, который запускается при запуске mjpg-streamer(port 9000). Второе окно подключено к web - серверу фреймворка WebIOPi (port 8000).
 
Рис.2. Интерфейс при работе с камерой. 
Этапы решения задачи:
 
1. Схема подключений
     На рисунке 3 показана схема подключения сервоприводов камеры к Raspberry Pi 2. Целесообразно сервоприводы подключать к отдельному от компьютера источнику питания.  
 

Рис.3. Схема подключения сервоприводов камеры к Raspberry P 2.

2. Установка фреймворка WebIOPi
Предполагается, что на Raspberry Pi 2 установлена операционная система RASPBIAN с IP адресом 192.168.1.38. 
Для работы 40 выводов порта GPIO целесообразно установить фреймворк WebIOPi версии 0.7.1: 

$ wget http://sourceforge.net/projects/webiopi/files/WebIOPi-0.7.1.tar.gz 
$ tar xvzf WebIOPi-0.7.1.tar.gz 
$ cd WebIOPi-0.7.1 
Устанавливаем  patch для работы с 40 пинами порта GPIO Raspberry Pi 2: 

$wget https://raw.githubusercontent.com/doublebind/raspi/master/webiopi-pi2bplus.patch 
$ patch -p1 -i webiopi-pi2bplus.patch 
$ sudo ./setup.sh 

Для автоматического старта WebIOPi после перегрузки компьютера необходимо выполнить команду (действительно для образа системы 2015-05-05-raspbian-wheezy.img): 
sudo update-rc.d webiopi defaults 

Для более поздних версий выполняются следующие команды:
$ cd /etc/systemd/system/ 
$ sudo wget https://raw.githubusercontent.com/doublebind/raspi/master/webiopi.service 
$ sudo systemctl start webiopi 
$ sudo systemctl enable webiopi

Редактируем конфигурационный файл config:

sudo nano /etc/webiopi/config
Настраиваем  порты GPIO 23,24  как выходы с низким стартовым уровнем. В секции [GPIO] записываем:

23 = OUT 0

24 = OUT 0

В секции [SCRIPTS] указываем имя и расположение файла - скрипта на Питоне, необходимый для управления сервоприводами

myproject = /home/pi/myproject/python/script.py

В секции [HTTP] указываем имя и расположение html файла:

doc-root = /home/pi/myproject/html

В секции [REST] прописываем активные порты(только они будут работать):

gpio-export = 23,24

После этого перегружаем Raspberry Pi 2: 

sudo reboot

Для входа по адресу http://192.168.1.38:8000 используется  логин - "webiopi" ,  пароль - "raspberry"

3. Настройка фреймворка WebIOPi. Построение файлов сценария на Python и HTML файла с JavaScript

Принимается, что сервоприводы поворачивают камеру Right-Left на угол -90...+90 градусов и Up-Down на угол -90...+90 градусов соответственно. Исходное состояние при старте или рестарте WebIOPi для Right-Left 0 градусов и Up-Down также 0 градусов.

В скрипте scrypt.py используются встроенные в WebIOPi функции:

GPIO.setFunction(SERVO1, GPIO.PWM) - устанавливает режим работы вывода  SERVO1, как ШИМ вывод.

GPIO.pwmWriteAngle(SERVO1, 0) - для сервопривода,подключенного к выводу SERVO1 устанавливает угол 0 градусов.

Задержка, формируемая командой webiopi.sleep(0.25) и последующее изменение режима работы вывода SERVO1 (GPIO.setFunction(SERVO1, GPIO.OUT)) необходимы  для предотвращения "дрожания" сервопривода (см. принцип работы сервопривода).

Аналогично для вывода SERVO2.

Файл сценария /home/pi/myproject/scrypt.py
# Imports
import webiopi
import datetime
GPIO = webiopi.GPIO
SERVO1=23
SERVO2=24
SERRL = 0 # Angle rotation Right-Left
STE = 0 # Step angle rotation
SERUD = 0 # Angle rotation Up-Down
# setup function is automatically called at WebIOPi startup
def setup():
# set the GPIO used by the light to output
   GPIO.setFunction(SERVO1, GPIO.PWM)
   GPIO.setFunction(SERVO2, GPIO.PWM)
   GPIO.pwmWriteAngle(SERVO1, 0)
   GPIO.pwmWriteAngle(SERVO2, 0)
   webiopi.sleep(0.25)
   GPIO.setFunction(SERVO1, GPIO.OUT)
   GPIO.setFunction(SERVO2, GPIO.OUT)

# destroy function is called at WebIOPi shutdown
def destroy():
   GPIO.digitalWrite(SERVO1, GPIO.OUT)
   GPIO.digitalWrite(SERVO2, GPIO.OUT)
@webiopi.macro
def steps():
   global SERRL,STE
   return "%d" % SERRL
@webiopi.macro
def steps1():
   global SERUD,STE
   return "%d" % SERUD

@webiopi.macro
def gets():
   global SERRL
   return "%d" % STE

@webiopi.macro
def lef(on):
   global SERRL,STE
   STE = int(on)
   GPIO.setFunction(SERVO1, GPIO.PWM)
   SERRL=SERRL+STE
   if (abs(SERRL)>=90):
       SERRL=90
   GPIO.pwmWriteAngle(SERVO1, SERRL)
   webiopi.sleep(0.25)
   GPIO.setFunction(SERVO1, GPIO.OUT)
   return gets()

@webiopi.macro
def ri(on):
   global SERRL,STE
   STE = int(on)
   GPIO.setFunction(SERVO1, GPIO.PWM)
   SERRL=SERRL-STE
   if (abs(SERRL)>=90):
       SERRL=-90
   GPIO.pwmWriteAngle(SERVO1, SERRL)
   webiopi.sleep(0.25)
   GPIO.setFunction(SERVO1, GPIO.OUT)
   return gets()

@webiopi.macro
def downs(on):
   global SERUD,STE
   STE = int(on)
   GPIO.setFunction(SERVO2, GPIO.PWM)
   SERUD=SERUD+STE
   if (abs(SERUD)>=90):
       SERUD=90
   GPIO.pwmWriteAngle(SERVO2, SERUD)
   webiopi.sleep(0.25)
   GPIO.setFunction(SERVO2, GPIO.OUT)
   return gets()

@webiopi.macro
def ups(on):
   global SERUD,STE
   STE = int(on)
   GPIO.setFunction(SERVO2, GPIO.PWM)
   SERUD=SERUD-STE
   if (abs(SERUD)>=90):
       SERUD=-90
   GPIO.pwmWriteAngle(SERVO2, SERUD)
   webiopi.sleep(0.25)
   GPIO.setFunction(SERVO2, GPIO.OUT)
   return gets()

HTML файл /home/pi/myproject/index.html

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>servo and camera</title>
<script type="text/javascript" src="/webiopi.js"></script>
<script type="text/javascript">
setInterval ("callMacro_steps()", 500);{
}
function callMacro_steps(){
webiopi().callMacro("steps", [], macro_steps_Callback);
}
function macro_steps_Callback(macro, args, data) {
$("#step").text("Right Left: "+data);
}
setInterval ("callMacro_steps1()", 500);{
}
function callMacro_steps1(){
webiopi().callMacro("steps1", [], macro_steps1_Callback);
}
function macro_steps1_Callback(macro, args, data) {
$("#step1").text("Up Down: "+data);
}
 webiopi().ready(function() {
        // Following function will process data received from set/gets macro.
        var update = function(macro, args, response) {
                    var servo=response;
                    // Following lines use jQuery functions
                    $("#inputOn").val(servo);
        }
        // Immediately call gets macro to update the UI with current values
        // "gets" refers to macro name
                // [] is an empty array, because gets macro does not take any argument
                // update is the callback function, defined above
                webiopi().callMacro("gets", [], update);
        // Create a button to call lef macro
        var sendButton1 = webiopi().createButton("sendButton1", "Left", function() {
           // Arguments sent to the macro
                    var servo = $("#inputOn").val();
            // Call the macro
                    webiopi().callMacro("lef", servo, update);
                });
        // Append the button to the controls box using a jQuery function
                $("#controls").append(sendButton1);
        // Create a button to call ri macro
        var sendButton2 = webiopi().createButton("sendButton2", "Right", function() {
           // Arguments sent to the macro
                    var servo = $("#inputOn").val();
            // Call the macro
                    webiopi().callMacro("ri", servo, update);
                });
        // Append the button to the controls box using a jQuery function
                $("#controls").append(sendButton2);
//-------------
        // Create a button to call downs macro
        var sendButton3 = webiopi().createButton("sendButton3", "Down", function() {
           // Arguments sent to the macro
                    var servo = $("#inputOn").val();
            // Call the macro
                    webiopi().callMacro("downs", servo, update);
                });
        // Append the button to the controls box using a jQuery function
                $("#controls").append(sendButton3);
        // Create a button to call ups macro
        var sendButton4 = webiopi().createButton("sendButton4", "Up", function() {
           // Arguments sent to the macro
                    var servo = $("#inputOn").val();
            // Call the macro
                    webiopi().callMacro("ups", servo, update);
                });
        // Append the button to the controls box using a jQuery function
                $("#controls").append(sendButton4);
//-------------
        // Refresh GPIO buttons
        // pass true to refresh repeatedly of false to refresh once
        webiopi().refreshGPIO(true);
        });
   </script>
         <style type="text/css">
               #step, #step1 {
                margin: 5px 5px 5px 5px;
                        width: 160px;
                        height: 45px;
                        background-color: Blue;
                font-size: 16pt;
                font-weight: bold;
                color: white;
        }
        </style>
    <style type="text/css">
        button {
            display: block;
            margin: 5px 5px 5px 5px;
                width: 160px;
                height: 45px;
            font-size: 20pt;
            font-weight: bold;
            color: white;
        }
    </style>
</head>
<body>
<div align="center">
Input step:<input type="text" maxlength="2" size="2" id="inputOn" /><br/>
<div id="controls"></div>
<div id="step"></div>
<div id="step1"></div>
</div>
</body>
</html>
 
4. Установка и настройка MJPG-Streamer (https://miguelmota.com/blog/raspberry-pi-camera-board-video-streaming/)
Предположим, наш текущий каталог /home/pi. Создадим каталог:
sudo mkdir /opt/mjpg-streamer

Установим библиотеку libjpeg62-dev:
sudo apt-get install libjpeg62-dev

Устанавливаем cmake:
sudo apt-get install cmake

Загружаем mjpg-streamer с плагином raspicam:
git clone https://github.com/jacksonliam/mjpg-streamer.git ~/mjpg-streamer

Заходим в директорию:
cd ~/mjpg-streamer/mjpg-streamer-experimental

Выполняем компиляцию
make clean all

Перемещаем файлы и удаляем рабочую директорию:
sudo mv ~/mjpg-streamer/mjpg-streamer-experimental /opt/mjpg-streamer
sudo rm -rf ~/mjpg-streamerСоздаем файл /opt/mjpg-streamer/start_stream.sh для старта MJPG-Streamer:

Создаем файл /opt/mjpg-streamer/start_stream.sh для старта MJPG-Streamer:
#!/bin/bash

if pgrep mjpg_streamer > /dev/null
then
echo "mjpg_streamer already running"
else
LD_LIBRARY_PATH=/opt/mjpg-streamer/ /opt/mjpg-streamer/mjpg_streamer -i "input_raspicam.so -fps 10 -q 50 -x 640 -y 480" -o "output_http.so -p 9000 -w /opt/mjpg-streamer/www" > /dev/null 2>&1&
echo "mjpg_streamer started"
fi

Создаем файл /opt/mjpg-streamer/stop_stream.sh для останова MJPG-Streamer:
#!/bin/bash
if pgrep mjpg_streamer
then
kill $(pgrep mjpg_streamer) > /dev/null 2>&1
echo "mjpg_streamer stopped"
else
echo "mjpg_streamer not running"
fi

Таким образом для запуска необходима команда:

/opt/mjpg-streamer/stop_stream.sh

 Для останова:

/opt/mjpg-streamer/stop_stream.sh

Просмотреть видеопоток можно, набрав в браузере адрес:

http://192.168.1.38:9000/stream_simple.html
Получим изображение, как в левом окне рисунка 2.
 
Выводы
1. Сервоприводы чувствительны к броскам напряжения. Например, при включении обогревателя сервоприводы "дергаются" и меняется положение камеры.
2. Замечено "подвисание" WebIOPi при управлении камерой. После рестарта по команде /etc/init.d/webiopi restart, управление камерой возобновляется.