Интерпретация нажатий клавиш, отправляемых в raspberry-pi через канал данных uv4l-webrtc

Я прошу прощения, если это не имеет смысла, так как я все еще новичок в использовании Raspberry Pi, и это мой первый пост в Stackru.

Я делаю веб-приложение, которое позволяет передавать потоковое видео на Rasberry Pi и с него, а также отправлять коды клавиш. Отправленные коды в конечном итоге позволили бы мне управлять сервоприводами на дроне. Поискав в интернете, я решил, что самый простой способ для потоковой передачи двухстороннего видео - это использование uv4l, поэтому я установил его вместе с uv4l-webrtc на моем raspberry pi. Я подключил несколько выводов GPIO к контроллеру полета и использую pigpio для отправки на него сигналов ШИМ, которые я затем отслеживаю с помощью CleanFlight.

Прямо сейчас, я могу манипулировать с помощью клавиш нажатия рулона, высоты тона и т. Д. Контроллера полета, используя скрипт Python, если я получаю удаленный доступ к пи с помощью VNC, но я бы хотел в конечном итоге сделать это через свою пользовательскую веб-страницу, которая обслуживается uv4l-сервером. Я пытаюсь использовать каналы данных WebRTC, но у меня возникают проблемы с пониманием того, что мне нужно сделать, чтобы распознать сообщения, отправленные по каналам данных. Я знаю, что каналы данных открываются, когда инициируется видеозвонок, и я попробовал выполнить тест по этой ссылке, чтобы посмотреть, действительно ли я могу отправить коды клавиш на пи (и я могу).

Моя проблема сейчас в том, что я понятия не имею, куда отправляются эти отправленные сообщения или как я могу их получить, чтобы я мог включить их в свой скрипт на python. Нужно ли мне создать сервер, который будет прослушивать коды клавиш, отправленные на пи?

У меня есть скрипт Python на Raspberry Pi для управления сервоприводами на контроллере полета с помощью клавиш и отдельной веб-страницы, которая транслирует видео с помощью WebRTC, но я не знаю, как объединить их вместе, используя каналы данных WebRTC.

Спасибо @adminkiam за решение. Вот версия скрипта Python, который теперь слушает сокет. По сути, это вариация этого кода от человека, который сделал pigpio:

import socket
import time
import pigpio

socket_path = '/tmp/uv4l.socket'

try:
    os.unlink(socket_path)
except OSError:
    if os.path.exists(socket_path):
        raise

s = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET)

ROLL_PIN     = 13
PITCH_PIN    = 14
YAW_PIN      = 15

MIN_PW = 1000
MID_PW = 1500
MAX_PW = 2000

NONE        = 0
LEFT_ARROW  = 1
RIGHT_ARROW = 2
UP_ARROW    = 3
DOWN_ARROW  = 4
LESS_BTN    = 5
GREATER_BTN = 6

print 'socket_path: %s' % socket_path
s.bind(socket_path)
s.listen(1)

def getch(keyCode):
    key = NONE
    if keyCode == 188:
        key = LESS_BTN
    elif keyCode == 190:
        key = GREATER_BTN
    elif keyCode == 37:
        key = LEFT_ARROW
    elif keyCode == 39:
        key = RIGHT_ARROW
    elif keyCode == 38:
        key = UP_ARROW
    elif keyCode == 40:
        key = DOWN_ARROW
    return key

def cleanup():
    pi.set_servo_pulsewidth(ROLL_PIN, 0)
    pi.set_servo_pulsewidth(PITCH_PIN, 0)
    pi.set_servo_pulsewidth(YAW_PIN, 0)
    pi.stop()

while True:
    print 'awaiting connection...'
    connection, client_address = s.accept()
    print 'client_address %s' % client_address
    try:
        print 'established connection with', client_address

        pi = pigpio.pi()

        rollPulsewidth     = MID_PW
        pitchPulsewidth    = MID_PW
        yawPulsewidth      = MID_PW

        pi.set_servo_pulsewidth(ROLL_PIN, rollPulsewidth)
        pi.set_servo_pulsewidth(PITCH_PIN, pitchPulsewidth)
        pi.set_servo_pulsewidth(YAW_PIN, yawPulsewidth)

        while True:
            data = connection.recv(16)
            print 'received message"%s"' % data

            time.sleep(0.01)
            key = getch(int(data))

            rollPW     = rollPulsewidth
            pitchPW    = pitchPulsewidth
            yawPW      = yawPulsewidth

            if key == UP_ARROW:
                pitchPW = pitchPW + 10
                if pitchPW > MAX_PW:
                    pitchPW = MAX_PW
            elif key == DOWN_ARROW:
                pitchPW = pitchPW - 10
                if pitchPW < MIN_PW:
                    pitchPW = MIN_PW
            elif key == LEFT_ARROW:
                rollPW = rollPW - 10
                if rollPW < MIN_PW:
                    rollPW = MIN_PW
            elif key == RIGHT_ARROW:
                rollPW = rollPW + 10
                if rollPW > MAX_PW:
                    rollPW = MAX_PW
            elif key == GREATER_BTN:
                yawPW = yawPW + 10
                if yawPW > MAX_PW:
                    yawPW = MAX_PW
            elif key == LESS_BTN:
                yawPW = yawPW - 10
                if yawPW < MIN_PW:
                    yawPW = MIN_PW

            if rollPW != rollPulsewidth:
                rollPulsewidth = rollPW
                pi.set_servo_pulsewidth(ROLL_PIN, rollPulsewidth)
            if pitchPW != pitchPulsewidth:
                pitchPulsewidth = pitchPW
                pi.set_servo_pulsewidth(PITCH_PIN, pitchPulsewidth)
            if yawPW != yawPulsewidth:
                yawPulsewidth = yawPW
                pi.set_servo_pulsewidth(YAW_PIN, yawPulsewidth)

            if data:
                print 'echo data to client'
                connection.sendall(data)
            else:
                print 'no more data from', client_address
                break

    finally:
        # Clean up the connection
        cleanup()
        connection.close()

1 ответ

Решение

Когда канал данных WebRTC создается между UV4L и другим одноранговым узлом WebRTC (т. Е. Браузером, шлюзом Janus и т. Д.), UV4L создает полнодуплексный сокет домена Unix (по умолчанию /tmp/uv4l.socket) из / в которые вы можете получать / отправлять сообщения на Raspberry Pi. Ваш скрипт на python должен просто открывать, прослушивать и читать сокеты для входящих сообщений, например, из веб-приложения, и / или записывать сообщения в тот же сокет, чтобы веб-приложение получало их. Пример, делающий это в C++, находится по ссылке на учебник, который вы указали в своем вопросе:

/*
    Copyright (c) 2016 info@linux-projects.org
    All rights reserved.

    Redistribution and use in source and binary forms are permitted
    provided that the above copyright notice and this paragraph are
    duplicated in all such forms and that any documentation,
    advertising materials, and other materials related to such
    distribution and use acknowledge that the software was developed
    by the linux-projects.org. The name of the
    linux-projects.org may not be used to endorse or promote products derived
    from this software without specific prior written permission.
    THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/

/*
 * This is a simple echo server.
 * It creates to a unix domain socket of type SOCK_SEQPACKET specified by
 * command line, listens to it waiting for incoming messages from clients
 * (e.g. UV4L) and replies the received messages back to the senders.
 *
 * Example:
 *     $ ./datachannel_server /tmp/uv4l.socket
 *
 * To compile this program you need boost v1.60 or greater, for example:
 * g++ -Wall -I/path/to/boost/include/ -std=c++11 datachannel_server.cpp -L/path/to/boost/lib -l:libboost_coroutine.a -l:libboost_context.a -l:libboost_system.a -l:libboost_thread.a -pthread -o datachannel_server
 */

#include <boost/asio/io_service.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/write.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio.hpp>
#include <memory>
#include <cstdio>
#include <array>
#include <functional>
#include <iostream>

#if !defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
#error Local sockets not available on this platform.
#endif

constexpr std::size_t MAX_PACKET_SIZE = 1024 * 16;

namespace seqpacket {

    struct seqpacket_protocol {

        int type() const {
            return SOCK_SEQPACKET;
        }

        int protocol() const {
            return 0;
        }

        int family() const {
            return AF_UNIX;
        }

        using endpoint = boost::asio::local::basic_endpoint<seqpacket_protocol>;
        using socket = boost::asio::generic::seq_packet_protocol::socket;
        using acceptor = boost::asio::basic_socket_acceptor<seqpacket_protocol>;

#if !defined(BOOST_ASIO_NO_IOSTREAM)
        /// The UNIX domain iostream type.
        using iostream = boost::asio::basic_socket_iostream<seqpacket_protocol>;
#endif
    };
}

using seqpacket::seqpacket_protocol;

struct session : public std::enable_shared_from_this<session> {
    explicit session(seqpacket_protocol::socket socket) : socket_(std::move(socket)) {}

    ~session() {
        //std::cerr << "session closed\n";
    }

    void echo(boost::asio::yield_context yield) {
        auto self = shared_from_this();
        try {
            for (;;) {
                seqpacket_protocol::socket::message_flags in_flags = MSG_WAITALL, out_flags = MSG_WAITALL;

                // Wait for the message from the client
                auto bytes_transferred = socket_.async_receive(boost::asio::buffer(data_), in_flags, yield);

                // Write the same message back to the client
                socket_.async_send(boost::asio::buffer(data_, bytes_transferred), out_flags, yield);
            }
        } catch (const std::exception& e) {
            std::cerr << e.what() << '\n';
            socket_.close();
        }
    }

    void go() {
        boost::asio::spawn(socket_.get_io_service(), std::bind(&session::echo, this, std::placeholders::_1));
    }

private:
    seqpacket_protocol::socket socket_;
    std::array<char, MAX_PACKET_SIZE> data_;
};

int main(int argc, char* argv[]) {
    try {
        if (argc != 2) {
            std::cerr << "Usage: datachannel_server <file> (e.g. /tmp/uv4l.socket)\n";
            std::cerr << "*** WARNING: existing file is removed ***\n";
            return EXIT_FAILURE;
        }

        boost::asio::io_service io_service;

        std::remove(argv[1]);

        boost::asio::spawn(io_service, [&](boost::asio::yield_context yield) {
                    seqpacket_protocol::acceptor acceptor_(io_service, seqpacket_protocol::endpoint(argv[1]));
                    for (;;) {
                        boost::system::error_code ec;
                        seqpacket_protocol::socket socket_(io_service);
                        acceptor_.async_accept(socket_, yield[ec]);
                        if (!ec)
                            std::make_shared<session>(std::move(socket_))->go();
                    }
                });

        io_service.run();

    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << "\n";
        return EXIT_FAILURE;
    }
}
Другие вопросы по тегам