1

如果这没有意义,我深表歉意,因为我仍然是使用树莓派的新手,这是我第一次在 StackOverflow 上发帖。

我正在制作一个网络应用程序,它可以让我在树莓派之间传输视频,同时还可以让我发送密钥代码。发送的键码最终会让我控制无人机上的伺服系统。在搜索了互联网之后,我发现流式传输 2 路视频的最简单方法是使用 uv4l,因此我将它与 uv4l-webrtc 一起安装在我的树莓派上。我将一些 GPIO 引脚连接到飞行控制器,并使用 pigpio 向其发送 PWM 信号,然后我使用 CleanFlight 对其进行监控。

现在,如果我使用VNC远程访问 pi,我可以使用 python 脚本通过按键操作飞行控制器的滚动、俯仰等,但我希望最终能够通过我的自定义网页来做到这一点由 uv4l-server 提供服务。我正在尝试使用 WebRTC 数据通道,但我无法理解如何识别通过数据通道发送的消息。我知道发起视频通话时会打开数据通道,并且我已尝试在此链接中进行测试以查看我是否确实可以将密钥代码发送到 pi(并且我可以)。

我现在的问题是我不知道那些发送的消息去哪里了,也不知道如何获取它们,所以我可以将它们合并到我的 python 脚本中。我需要制作一个服务器来监听发送到 pi 的键码吗?

tl; dr 我在树莓派上有一个 python 脚本,可以使用按键和一个单独的网页来控制飞行控制器上的伺服系统,该网页使用 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()
4

1 回答 1

1

当在 UV4L 和其他 WebRTC 对等点(即浏览器、Janus 网关等)之间创建 WebRTC 数据通道时,UV4L 会从/到创建全双工 Unix 域套接字(默认为 /tmp/uv4l.socket)您可以在 Raspberry Pi 上接收/发送消息。您的 python 脚本应该只打开、侦听和读取来自例如 Web 应用程序的传入消息的套接字和/或将消息写入同一个套接字以供 Web 应用程序接收它们。在 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;
    }
}
于 2017-07-28T11:38:09.230 回答