查看: 4108|回复: 2

[项目] Windows系统监听键盘通过UDP协议控制树莓派小车

[复制链接]
  • TA的每日心情

    2020-3-6 09:52
  • 签到天数: 13 天

    连续签到: 1 天

    [LV.3]偶尔看看II

    发表于 2018-10-12 15:01:48 | 显示全部楼层 |阅读模式
    分享到:
    树莓派小车硬件从淘宝买到手后已经鼓捣很长时间了,其中最喜欢的应用是控制小车运动了。我的小车控制系统在开发的过程中遇到了很多小问题,都被我一一修正了,将开发经验与大家分享,希望后来人能少走些弯路。

    控制小车的方法大体上有以下几种:
    1.TCP/UDP控制;
    2.网页控制;
    3.蓝牙控制;
    4.红外遥控器控制;
    5.手柄控制。


    本次我采用的是【1.TCP/UDP控制】方案。
    需要的硬件如下:

    1. win7笔记本
    2. 树莓派小车
    3. 无线wifi网络


    控制方案简介:通过监听win7笔记本上的键盘的按键,获得控制信息。然后将控制信息通过UDP协议发送给局域网内的树莓派小车。树莓派小车获得指令后进行动作。

    小车控制系统分为三部分:控制协议、客户端设计、服务端设计。


    0×01 控制协议

    小车控制系统的通讯协议采用面向非连接的UDP协议,相比TCP协议,UDP协议更简单有效。
    控制协议格式如下:

    协议版本号:包号:命令字

    其中协议版本号固定为1;包号为一个数字,每发送一次进行加1处理;命令字用于向小车发送控制命令。

    本文用到的命令字有如下几种。

    1. //方向控制
    2. #define CARRUN_REMOTE_FORWARD   (0x01)
    3. #define CARRUN_REMOTE_BACK      (0x02)
    4. #define CARRUN_REMOTE_LEFT      (0x03)
    5. #define CARRUN_REMOTE_RIGHT     (0x04)
    6. #define CARRUN_REMOTE_STOP      (0x05)

    7. //IP相关命令
    8. #define CARRUN_REMOTE_WHOISONLINE    (0x06)  // who is online
    9. #define CARRUN_REMOTE_ONLINE         (0x07)  // I am online
    复制代码


    0×02 客户端设计

    客户端界面如下所示。

    1.jpg

    可以手动输入树莓派小车的IP地址。为了方面操作,设计了一个自动获得树莓派小车IP的功能。

    按下[Find Car]按钮,客户端程序向整个网络广播CARRUN_REMOTE_WHOISONLINE(谁在线)请求。当树莓派小车收到广播通知,立即向客户端发送CARRUN_REMOTE_ONLINE(我在线)的答复。客户端收到答复,自动将树莓派小车的IP地址记录到文本框中显示。

    获得树莓派小车IP地址后,按下[Start]按钮,开始监听键盘信息,并根据监听到的特定按键的按下、抬起事件,转换为树莓派小车的控制命令,通过UDP协议与树莓派小车通信。

    按键与小车动作的转换表如下:

    2.jpg

    UDP协议使用的3737端口,发送UDP协议是很简单的事情,本文不详细介绍。

    监听键盘是通过安装系统钩子实现的,具体内容不详细介绍,读者可以检索SetWindowsHookEx函数进行详细了解。


    0×03 服务器端设计

    树莓派小车安装的是RaspbianOS系统,该系统采用linux内核,对熟悉linux的朋友很容易上手。

    服务器端使用C++语言和python语言混合编程实现。其中python语言编写的程序用于控制小车的前进、后退、左转、右转、停止动作。C++语言编写的程序用于监听UDP命令,并处理控制命令。

    用python控制小车的代码非常简单,我调试好的代码如下:

    1. #!/usr/bin/Python
    2. # -*- coding: UTF-8 -*-

    3. #引入gpio的模块
    4. import RPi.GPIO as GPIO
    5. import time


    6. #设置in1到in4接口
    7. IN1 = 12
    8. IN2 = 16
    9. IN3 = 18
    10. IN4 = 22

    11. #初始化接口
    12. def car_init():
    13.     #设置GPIO模式
    14.     GPIO.setmode(GPIO.BOARD)

    15.     GPIO.setup(IN1,GPIO.OUT)
    16.     GPIO.setup(IN2,GPIO.OUT)
    17.     GPIO.setup(IN3,GPIO.OUT)
    18.     GPIO.setup(IN4,GPIO.OUT)

    19. #前进的代码
    20. def car_forward():
    21.     GPIO.output(IN1,GPIO.HIGH)
    22.     GPIO.output(IN2,GPIO.LOW)
    23.     GPIO.output(IN3,GPIO.HIGH)
    24.     GPIO.output(IN4,GPIO.LOW)
    25.     time.sleep(0.15)
    26.     GPIO.cleanup()

    27. #后退
    28. def car_back():
    29.     GPIO.output(IN1,GPIO.LOW)
    30.     GPIO.output(IN2,GPIO.HIGH)
    31.     GPIO.output(IN3,GPIO.LOW)
    32.     GPIO.output(IN4,GPIO.HIGH)
    33.     time.sleep(0.15)
    34.     GPIO.cleanup()

    35. #左转
    36. def car_left():
    37.     GPIO.output(IN1,False)
    38.     GPIO.output(IN2,False)
    39.     GPIO.output(IN3,GPIO.HIGH)
    40.     GPIO.output(IN4,GPIO.LOW)
    41.     time.sleep(0.15)
    42.     GPIO.cleanup()

    43. #右转
    44. def car_right():
    45.     GPIO.output(IN1,GPIO.HIGH)
    46.     GPIO.output(IN2,GPIO.LOW)
    47.     GPIO.output(IN3,False)
    48.     GPIO.output(IN4,False)
    49.     time.sleep(0.15)
    50.     GPIO.cleanup()

    51. #停止
    52. def car_stop():
    53.     GPIO.output(IN1,GPIO.LOW)
    54.     GPIO.output(IN2,GPIO.LOW)
    55.     GPIO.output(IN3,GPIO.LOW)
    56.     GPIO.output(IN4,GPIO.LOW)
    57.     GPIO.cleanup()
    复制代码
    C++语言编写的程序是小车控制系统的核心,可以分为4个模块:协议解析模块、UDP端口监听模块、逻辑控制模块、动作执行模块。

    协议解析模块用于解析协议。

    协议解析模块的核心代码如下:

    1. #define COMMUNICATION_PORT (3737)

    2. //command
    3. #define CARRUN_REMOTE_FORWARD   (0x01)
    4. #define CARRUN_REMOTE_BACK      (0x02)
    5. #define CARRUN_REMOTE_LEFT      (0x03)
    6. #define CARRUN_REMOTE_RIGHT     (0x04)
    7. #define CARRUN_REMOTE_STOP      (0x05)

    8. #define CARRUN_REMOTE_WHOISONLINE    (0x06)
    9. #define CARRUN_REMOTE_ONLINE         (0x07)

    10. #define GET_MODE(command) (command & 0x000000ffUL)

    11. struct MsgInfo {
    12.     unsigned long ipaddr;       // IP Address
    13.     int           portNo;       // port number

    14.     std::string   version;      // version
    15.     unsigned long packetNo;     // packet number
    16.     unsigned long command;      // command
    17. };

    18. #define SOCKET_ERROR            (-1)

    19. //消息解析函数
    20. bool ResolveMsg(const std::string message, MsgInfo &msg) {
    21.     std::vector<std::string> vInfos;  
    22.     boost::split( vInfos, message, boost::is_any_of( ",:" ), boost::token_compress_on );

    23.     if ( vInfos[0] != "1" ) {
    24.         std::cout << "protocol is wrong."<< std::endl;
    25.         return false;
    26.     }

    27.     msg.version  = vInfos[0];
    28.     msg.packetNo = atoi(vInfos[1].c_str());
    29.     msg.command  = atoi(vInfos[2].c_str());

    30.     return true;
    31. }

    32. unsigned long MakePacketNumber() {
    33.     static unsigned long packnumber = 0;
    34.     packnumber++;

    35.     return packnumber;
    36. }

    37. //发送UDP消息函数
    38. bool UDPSend(int localSocket, unsigned long host_addr, unsigned short port_no, unsigned long command )
    39. {
    40.     struct sockaddr_in    addr;

    41.     memset(&addr, 0, sizeof(addr));    /// 网络地址初始化
    42.     addr.sin_family         = AF_INET;
    43.     addr.sin_port           = htons(port_no);
    44.     addr.sin_addr.s_addr    = host_addr;

    45.     char message[512] = {0};
    46.     unsigned long packnumber = MakePacketNumber();

    47.     sprintf(message,"%d:%ld:%ld",0x1,packnumber,command);

    48.     if (SOCKET_ERROR == ::sendto(localSocket, message, strlen(message),0, (struct sockaddr *)&addr ,sizeof(addr))) {
    49.         return false;
    50.     }

    51.     return true;
    52. }
    复制代码


    UDP端口监听模块用于监听从3737端口来的控制信息,并将收到的消息解析后以异步的方式传递给逻辑控制模块。

    UDP端口监听模块的核心代码如下:

    1. int localUDPSocket;

    2. void *listenUDPCommunicationThread(void *arg) {
    3.     struct sockaddr_in localAddr,remoteAddr;
    4.     localUDPSocket = socket(AF_INET, SOCK_DGRAM, 0);  // udp
    5.     bzero(&localAddr, sizeof(localAddr));
    6.     localAddr.sin_family = AF_INET;
    7.     localAddr.sin_port = htons(COMMUNICATION_PORT);   // port
    8.     localAddr.sin_addr.s_addr = INADDR_ANY;

    9.     std::cout << "socket returned : " << localUDPSocket << std::endl;

    10.     int result = bind(localUDPSocket, (struct sockaddr *)&localAddr, sizeof(localAddr));
    11.     std::cout << "bind returned : " << result << std::endl <<std::endl;

    12.     if (-1 == result) {
    13.         std::cout << "bind error!" << std::endl;
    14.         exit(1);
    15.     }

    16.     char buffer[1024] = {0};

    17.     while(1) {   
    18.         memset(buffer,0,sizeof(buffer));
    19.         socklen_t remoteAddrLength = sizeof(remoteAddr);
    20.         int nReceiveSize = recvfrom(localUDPSocket, &buffer, sizeof(buffer), 0, (struct sockaddr *)&remoteAddr, &remoteAddrLength);

    21.         std::cout << "received UDP data. data is: " << buffer << std::endl;

    22.         std::string message(buffer,nReceiveSize);

    23.         DoAction(&remoteAddr, message);
    24.     }
    25. }

    26. void DoAction(struct sockaddr_in *addr, const std::string message) {
    27.     MsgInfo msg;
    28.     if (addr) {
    29.         msg.ipaddr = addr->sin_addr.s_addr;
    30.         msg.portNo = htons(addr->sin_port);   
    31.     }
    32.    

    33.     bool bResult = ResolveMsg( message, msg );
    34.     if ( !bResult ) {
    35.         std::cout << "ResolveMsg fail."<< std::endl;

    36.         return;
    37.     }

    38. //    std::cout << "command:0x" << hex << msg.command << std::endl;

    39.     switch ( GET_MODE(msg.command) )
    40.     {
    41.     case CARRUN_REMOTE_FORWARD:
    42.         {
    43.             std::cout << "command: CARRUN_REMOTE_FORWARD"<< std::endl;
    44.         
    45.             DirectionReq *req = new DirectionReq();
    46.             req->setValue(DIRECTION_FORWARD);
    47.             ControlManager::instance()->postActionReq(req);   
    48.         }
    49.         

    50.         break;
    51.     case CARRUN_REMOTE_BACK:
    52.         {
    53.             std::cout << "command: CARRUN_REMOTE_BACK"<< std::endl;
    54.         
    55.             DirectionReq *req = new DirectionReq();
    56.             req->setValue(DIRECTION_BACK);
    57.             ControlManager::instance()->postActionReq(req);
    58.         }
    59.         break;
    60.     case CARRUN_REMOTE_LEFT:
    61.         {
    62.             std::cout << "command: CARRUN_REMOTE_LEFT"<< std::endl;
    63.         
    64.             DirectionReq *req = new DirectionReq();
    65.             req->setValue(DIRECTION_LEFT);
    66.             ControlManager::instance()->postActionReq(req);   
    67.         }
    68.         break;
    69.     case CARRUN_REMOTE_RIGHT:
    70.         {
    71.             std::cout << "command: CARRUN_REMOTE_RIGHT"<< std::endl;
    72.         
    73.             DirectionReq *req = new DirectionReq();
    74.             req->setValue(DIRECTION_RIGHT);
    75.             ControlManager::instance()->postActionReq(req);   
    76.         }
    77.         break;

    78.     case CARRUN_REMOTE_STOP:
    79.         {
    80.             std::cout << "command: CARRUN_REMOTE_STOP"<< std::endl;
    81.         
    82.             StatusReq *req = new StatusReq();
    83.             ControlManager::instance()->postStatusReq(req);   
    84.         }
    85.         break;
    86.         
    87.     case CARRUN_REMOTE_WHOISONLINE:
    88.         std::cout << "command: CARRUN_REMOTE_WHOISONLINE"<< std::endl;
    89.         UDPSend(localUDPSocket,addr->sin_addr.s_addr,COMMUNICATION_PORT,CARRUN_REMOTE_ONLINE);
    90.         break;



    91.     default:
    92.         {
    93.             std::cout << "Unknown command:"<< msg.command << std::endl;
    94.         }
    95.         break;
    96.     }

    97.     return;

    98. }
    复制代码
    逻辑控制模块只有一个线程在处理,但有两个队列。队列1优先级最高,只存放小车的停止命令。队列2优先级最低,用于存放小车的前进、后退、左转、右转命令。

    由于键盘按键按下后如果一直不动,那么客户端(PC)会向服务器端(树莓派小车)连续发送大量的命令,由于树莓派小车的处理速度远低于消息发送的速度,此时很容易在树莓派小车产生消息堆积,这样会导致控制失灵。

    解决办法:当树莓派小车收到停止命令时,将消息投递到优先级最高的队列1中。当逻辑控制模块每处理一个命令后,会优先检查队列1中是否有停止命令,如果有停止命令的话,会执行让小车停止动作,同时清空队列2中的全部堆积指令。

    逻辑控制模块的核心代码如下。

    1. void ReqThread::Run()
    2. {
    3.         while (1)
    4.         {
    5.                 if ( m_directionlist.size()  > 0)
    6.                 {
    7.                         pthread_mutex_lock(&mutex);
    8.                         DirectionReq* pReq = (DirectionReq*)m_directionlist.front();
    9.                         pthread_mutex_unlock(&mutex);
    10.                         if (NULL != pReq) {
    11.                                 pReq->doAction();
    12.                                 delete pReq;
    13.                                 pReq = NULL;
    14.                         }
    15.             pthread_mutex_lock(&mutex);      
    16.                         m_directionlist.erase(m_directionlist.begin());
    17.             pthread_mutex_unlock(&mutex);
    18.                 }

    19.                 if ( m_statuslist.size()  > 0)
    20.                 {
    21.                         pthread_mutex_lock(&mutex);
    22.                         StatusReq* pReq = (StatusReq*)m_statuslist.front();
    23.                         pthread_mutex_unlock(&mutex);
    24.                         if (NULL != pReq) {
    25.                                 pReq->doAction();
    26.                                 delete pReq;
    27.                                 pReq = NULL;
    28.                         }

    29.                         pthread_mutex_lock(&mutex);
    30.                         m_statuslist.erase(m_statuslist.begin());
    31.             pthread_mutex_unlock(&mutex);

    32.                         clearAllList();
    33.                 }

    34.         }
    35. }

    36. void ReqThread::clearAllList()
    37. {
    38.     pthread_mutex_lock(&mutex);

    39.         if (m_statuslist.size()  > 0) {
    40.                 std::cout << "clear statuslist"<< std::endl;               
    41.         }

    42.         while (m_statuslist.size()  > 0)
    43.         {
    44.                 RequestBase* pReq = m_statuslist.front();
    45.                 if (NULL != pReq) {
    46.                         delete pReq;
    47.                         pReq = NULL;
    48.                 }
    49.         
    50.                 m_statuslist.erase(m_statuslist.begin());
    51.         }

    52.         if (m_directionlist.size()  > 0) {
    53.                 std::cout << "clear directionlist"<< std::endl;               
    54.         }

    55.         while (m_directionlist.size()  > 0)
    56.         {
    57.                 RequestBase* pReq = m_directionlist.front();
    58.                 if (NULL != pReq) {
    59.                         delete pReq;
    60.                         pReq = NULL;
    61.                 }
    62.         
    63.                 m_directionlist.erase(m_directionlist.begin());
    64.         }

    65.     pthread_mutex_unlock(&mutex);
    66. }
    复制代码
    动作执行模块用于调用python模块的接口,实现小车的前进、后退、左转、右转,停止动作。

    注意:C++代码是可以调用python模块的接口的。

    动作执行模块的代码如下。

    1. #include </usr/include/python2.7/Python.h>   
    2. #include <iostream>
    3. #include "switch.h"

    4. #define DEBUG_TEST 0

    5. void Forward()
    6. {
    7.     std::cout<<"###Forward###"<<std::endl;

    8. #if DEBUG_TEST
    9.     sleep(1);
    10.     return;
    11. #endif

    12.     //初始化python
    13.     Py_Initialize();

    14.     PyRun_SimpleString("import sys");
    15.     PyRun_SimpleString("sys.path.append('./')");

    16.         PyObject * pModule = NULL;
    17.     PyObject * pFunc = NULL;

    18.     //这里是要调用的文件名
    19.     pModule = PyImport_ImportModule("control");
    20.     if (!pModule)
    21.     {
    22.         std::cout<<"Call PyImport_ImportModule("control")  fail."<<std::endl;
    23.         return;
    24.     }

    25.     //这里是要调用的函数名
    26.     pFunc= PyObject_GetAttrString(pModule, "car_init");
    27.    
    28.     //调用函数
    29.     PyEval_CallObject(pFunc, NULL);
    30.     Py_DECREF(pFunc);  


    31.     //这里是要调用的函数名
    32.     pFunc= PyObject_GetAttrString(pModule, "car_forward");

    33.     //调用函数
    34.     PyEval_CallObject(pFunc, NULL);
    35.     Py_DECREF(pFunc);
    36. }

    37. void Back()
    38. {
    39.     std::cout<<"###Back###"<<std::endl;

    40. #if DEBUG_TEST
    41.     sleep(1);
    42.     return;
    43. #endif

    44.     //初始化python
    45.     Py_Initialize();

    46.     PyRun_SimpleString("import sys");
    47.     PyRun_SimpleString("sys.path.append('./')");

    48.         PyObject * pModule = NULL;
    49.     PyObject * pFunc = NULL;

    50.     //这里是要调用的文件名
    51.     pModule = PyImport_ImportModule("control");
    52.     if (!pModule)
    53.     {
    54.         std::cout<<"Call PyImport_ImportModule("control")  fail."<<std::endl;
    55.         return;
    56.     }

    57.     //这里是要调用的函数名
    58.     pFunc= PyObject_GetAttrString(pModule, "car_init");
    59.     //调用函数
    60.     PyEval_CallObject(pFunc, NULL);
    61.     Py_DECREF(pFunc);  

    62.     //这里是要调用的函数名
    63.     pFunc= PyObject_GetAttrString(pModule, "car_back");
    64.     //调用函数
    65.     PyEval_CallObject(pFunc, NULL);
    66.     Py_DECREF(pFunc);  
    67. }

    68. void Left()
    69. {
    70.     std::cout<<"###Left###"<<std::endl;

    71. #if DEBUG_TEST
    72.     sleep(1);
    73.     return;
    74. #endif

    75.     //初始化python
    76.     Py_Initialize();

    77.     PyRun_SimpleString("import sys");
    78.     PyRun_SimpleString("sys.path.append('./')");

    79.         PyObject * pModule = NULL;
    80.     PyObject * pFunc = NULL;

    81.     //这里是要调用的文件名
    82.     pModule = PyImport_ImportModule("control");
    83.     if (!pModule)
    84.     {
    85.         std::cout<<"Call PyImport_ImportModule("control")  fail."<<std::endl;
    86.         return;
    87.     }

    88.     //这里是要调用的函数名
    89.     pFunc= PyObject_GetAttrString(pModule, "car_init");
    90.     //调用函数
    91.     PyEval_CallObject(pFunc, NULL);
    92.     Py_DECREF(pFunc);  

    93.     //这里是要调用的函数名
    94.     pFunc= PyObject_GetAttrString(pModule, "car_left");
    95.     //调用函数
    96.     PyEval_CallObject(pFunc, NULL);
    97.     Py_DECREF(pFunc);  
    98. }

    99. void Right()
    100. {
    101.     std::cout<<"###Right###"<<std::endl;

    102. #if DEBUG_TEST
    103.     sleep(1);
    104.     return;
    105. #endif

    106.     //初始化python
    107.     Py_Initialize();

    108.     PyRun_SimpleString("import sys");
    109.     PyRun_SimpleString("sys.path.append('./')");

    110.         PyObject * pModule = NULL;
    111.     PyObject * pFunc = NULL;

    112.     //这里是要调用的文件名
    113.     pModule = PyImport_ImportModule("control");
    114.     if (!pModule)
    115.     {
    116.         std::cout<<"Call PyImport_ImportModule("control")  fail."<<std::endl;
    117.         return;
    118.     }

    119.     //这里是要调用的函数名
    120.     pFunc= PyObject_GetAttrString(pModule, "car_init");
    121.     //调用函数
    122.     PyEval_CallObject(pFunc, NULL);
    123.     Py_DECREF(pFunc);  

    124.     //这里是要调用的函数名
    125.     pFunc= PyObject_GetAttrString(pModule, "car_right");
    126.     //调用函数
    127.     PyEval_CallObject(pFunc, NULL);
    128.     Py_DECREF(pFunc);

    129. }

    130. void Stop()
    131. {
    132.     std::cout<<"###Stop###"<<std::endl;
    133.    
    134. #if DEBUG_TEST
    135.     sleep(1);
    136.     return;
    137. #endif

    138.     //初始化python
    139.     Py_Initialize();

    140.     PyRun_SimpleString("import sys");
    141.     PyRun_SimpleString("sys.path.append('./')");
    142.    
    143.         PyObject * pModule = NULL;
    144.     PyObject * pFunc = NULL;

    145.     //这里是要调用的文件名
    146.     pModule = PyImport_ImportModule("control");
    147.     if (!pModule)
    148.     {
    149.         std::cout<<"Call PyImport_ImportModule("control")  fail."<<std::endl;
    150.         return;
    151.     }
    152.    
    153.     //这里是要调用的函数名
    154.     pFunc= PyObject_GetAttrString(pModule, "car_init");
    155.     //调用函数
    156.     PyEval_CallObject(pFunc, NULL);
    157.     Py_DECREF(pFunc);  

    158.     //这里是要调用的函数名
    159.     pFunc= PyObject_GetAttrString(pModule, "car_stop");
    160.     //调用函数
    161.     PyEval_CallObject(pFunc, NULL);
    162.     Py_DECREF(pFunc);
    163. }
    复制代码
    到此整个小车控制系统就介绍完了。

    最后,整个服务器端代码已经发到了百度网盘上。


    游客,如果您要查看本帖隐藏内容请回复


    本文作者xutiejun,转载自freebuf

    相关帖子

    回复

    使用道具 举报

    该用户从未签到

    发表于 2018-10-15 11:32:19 | 显示全部楼层
    学习学习学习学习学习学习学习学习学习学习学习学习
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /2 下一条



    手机版|小黑屋|与非网

    GMT+8, 2024-4-25 19:06 , Processed in 0.144981 second(s), 22 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.