2回答

2收藏

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

Raspberry Pi Raspberry Pi 5785 人阅读 | 2 人回复 | 2018-10-12

树莓派小车硬件从淘宝买到手后已经鼓捣很长时间了,其中最喜欢的应用是控制小车运动了。我的小车控制系统在开发的过程中遇到了很多小问题,都被我一一修正了,将开发经验与大家分享,希望后来人能少走些弯路。

控制小车的方法大体上有以下几种:
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 客户端设计

客户端界面如下所示。



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

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

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

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



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

关注下面的标签,发现更多相似文章
分享到:
回复

使用道具 举报

回答|共 2 个

倒序浏览

沙发

zhang013hao

发表于 2018-10-15 11:32:19 | 只看该作者

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

本版积分规则

关闭

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