远程控制小车(无限距离、实时摄像头画面)

        刷到过一个坐在家里遥控远程小车去店里取餐的视频,一直想自己复刻一个,参考了网上资料发现大多数资料发现都在教使用MQTT通讯来做,但是亲自尝试之后发现MQTT延时太高,无法做到实时画面的传输,在踩了众多坑之后,终于实现了远程无限距离控制、带实时摄像头画面的小车制作,分享下主要的制作细节

        大体思路是:使用FRP工具穿透阿里云服务器与本地计算机,穿透之后即可实现计算机与手机之间公网TCP通讯,手机端开发APP发送实时摄像头画面(用不用的旧安卓手机就行,手机需要连WIFI或者插卡),并接收来自电脑端的电机控制指令,手机APP使用OGT数据线和串口模块实现电机控制指令下发到小车单片机。

1.阿里云服务器的穿透

        首次开通阿里云服务器ECS会有一段时间的免费额度,还好在额度使用完之前下班之余抽空开发出来了,大家直接去阿里云官网开通就行,具体的配置思路可以参考下面这篇文章FRP穿透我的云服务ECS和本地电脑都是windows端。在进行下面的步骤之前,这一步一定要先完成。

2.本地个人电脑TCP通讯的程序(Visual Studio,C#语言)

        这里我写了四个标志位,分别是WSAD四个按键来表示小车的前行、后退、左转和右转,并且使用openCV实时显示摄像头画面。(偷懒没做UI界面,而且后续看能不能把这部分移植到手机端吧,毕竟在户外带着手机操作要方便点)

using System.Diagnostics;
using OpenCvSharp;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;

namespace TCPServer
{
    class Program
    {
        // 用于检测按键状态的WinAPI函数
        [DllImport("user32.dll")]
        private static extern short GetAsyncKeyState(int vKey);
        static bool isWPressed = false;
        static bool isSPressed = false;
        static bool isAPressed = false;
        static bool isDPressed = false;

        static void Main(string[] args)
        {
            int var1 = 0;
            int var2 = 0;
            // 启动按键监听线程
            Thread keyListenerThread = new Thread(KeyListener);
            keyListenerThread.IsBackground = true;
            keyListenerThread.Start();

            // 设置TCP服务器
            IPAddress localAddr = IPAddress.Any;
            TcpListener server = new TcpListener(localAddr, 22);
            server.Start();
            Console.WriteLine("Server started.");

            TcpClient client = server.AcceptTcpClient();
            NetworkStream stream = client.GetStream();

            try
            {
                while (true)
                {
                    // 根据按键状态设置 var1 的值
                    if (isWPressed)
                        var1 = 2;
                    else if (isSPressed)
                        var1 = 1;
                    else
                        var1 = 0;

                    if (isAPressed)
                        var2 = 2;
                    else if (isDPressed)
                        var2 = 1;
                    else
                        var2 = 0;

                    // 读取数据长度
                    byte[] lengthBuffer = new byte[4];
                    if (ReadFull(stream, lengthBuffer, 0, 4) != 4)
                        throw new Exception("Failed to read length");

                    int length = BitConverter.ToInt32(lengthBuffer, 0);
                    if (length < 0 || length > 10 * 1024 * 1024) // 添加长度校验,防止分配过大内存
                        throw new Exception("Invalid length");

                    // 读取数据
                    byte[] buffer = new byte[length];
                    if (ReadFull(stream, buffer, 0, length) != length)
                        throw new Exception("Failed to read image data");

                    Mat image = Cv2.ImDecode(buffer, ImreadModes.Color); // 解码图像
                    if (!image.Empty())
                    {
                        Cv2.ImShow("TCP Server", image);
                        Cv2.WaitKey(1); // 更新窗口
                    }

                    // 将 var1 和 var2 拼接为四字节字符串并发送
                    string message = var1.ToString("D2") + var2.ToString("D2");
                    byte[] data = Encoding.ASCII.GetBytes(message);
                    stream.Write(data, 0, data.Length);

                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error: " + ex.Message);
            }

            stream.Close();
            client.Close();
            server.Stop();
        }

        // 一个用于从NetworkStream中读取指定数量字节的辅助函数
        static int ReadFull(NetworkStream stream, byte[] buffer, int offset, int size)
        {
            int totalRead = 0;
            while (totalRead < size)
            {
                int read = stream.Read(buffer, offset + totalRead, size - totalRead);
                if (read == 0)
                {
                    break; // 连接可能已关闭
                }
                totalRead += read;
            }
            return totalRead;
        }

        static void KeyListener()
        {
            while (true)
            {
                // 检测按键状态
                if ((GetAsyncKeyState(0x57) & 0x8000) != 0) // w键
                {
                    isWPressed = true;
                    isSPressed = false;
                }
                else if ((
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值