找回密码
 马上注册

QQ登录

只需一步,快速开始

查看: 11243|回复: 5

从连接开始--EV3机器人与物联网云平台OneNet互连

[复制链接]
发表于 2015-10-10 09:56:48 | 显示全部楼层 |阅读模式
本帖最后由 shuxulala 于 2015-10-10 10:27 编辑



概述
早就听说乐高第三代机器人很强大,图形化编程简单易学,前不久买了一个legoEV3机器人玩具,体验了一把,的确很好玩,可以根据自己的需要自己搭建很多好玩的东东,看到网上有乐高机器人打印机、乐高机器人解魔方的例子,很是羡慕,不过这些例子都是传说中的乐高高级玩法而不是图形化编程来实现的,于是自己也想用高级语言来玩玩。由于工作的原因,断断续续玩了一阵,拖了好长一段时间,也没及时发帖记录过程,今天补上。

因为以前对lego机器人完全不懂,所以一开始在网上大肆搜索关于lego的资料,在网上关于lego的资料不多,主要就是中文乐高论坛,还有乐高的官方网站,这两个地方的资料足够初学者入门了。在中文乐高论坛里面,虽然帖子不多,但是对于初学乐高高级玩法的人,篇篇都是精华啊。乐高官网主要是下载乐高图形化编程软件、内核源码、用户说明书等资料。

乐高官网下载专区:http://www.lego.com/zh-cn/mindstorms/downloads

初步了解EV3,发现EV3是基于Linux/ARM系统,窃喜。因为自己以前就是搞嵌入式Linux这一块的,其中也有涉及ARM这块,所以觉得事情一下子变得简单了。对于这个改变,乐高开发者是非常英明的。对于开发商而言,可以利用Linux社区中的海量资源,省去了大量基础开发的时间;对于熟悉Linux的玩家而言,大大降低了学习的时间。因为基于ARMLinux系统作为基础,EV3可以在此基础上玩出很多花样,并不是简简单单的图形化编程,当然针对不同需求的玩家,每个人玩法和难度有所不同,今天我们所讲述的就是把EV3和一个新兴产业--物联网联系在一起,让EV3和物联网开发云平台OneNet连接,进行数据上传。

OneNet是中国移动自主开发的物联网开放云平台,提供事件告警、消息服务、数据存储转发等七大功能。功能详情请登录网址:http://open.iot.10086.cn/了解。笔者也玩点不一样的,把legoEV3作为物联网终端和OneNet连接,实现EV3自身状态数据的上传和展示。



准备工作

好了,言归正传,因为是第一次玩乐高,就不没有玩太复杂,本次目标:
  • 搭建一个lego机器人,图形化编程实现机器人跟着信标(红外信号遥控)移动,信标到哪儿,机器人就到哪儿;
  • 采集机器人传感器检测到的参数,通过网络传到OneNet平台,这里传输的参数包括机器人与信标距离、机器人移信标水平夹角、当前环境光照强度。当然乐高机器人可以检测到的数据不只这三个,比如还有光照颜色、是否有障碍物、陀螺仪输出的重力加速度数据等等。


实现目标可以分为两步:
  • 机器人搭建和动作控制编程(图形化编程实现);
  • 原生C++、C和主程序的通信;
  • 获取传感器数据并上传至OneNet(C语言实现)。


用到的硬件设备:
  • 乐高EV3机器人套件   
  • Netgear WNA1100无线WIFI模块,如下图:

QQ图片20151010100152.png


在开始之前,需要的软件上的准备工作如下:

1)安装LEGO MINDSTORMS EV3 Home Edition软件

该软件在乐高官方网站的下载专区可以下载,是官方发布的乐高EV3图形化编程环境,里面包含了搭建实例,以及图形化编程指导等等内容。可以通过该软件完成机器人图形化编程。

2)安装BricxCommand Center (BricxCC)软件

BricxCC软件就不多介绍了,论坛里面已有相关的详细帖子,具体安装方法,请参见:http://bbs.cmnxt.com/thread-13374-1-1.html  

除此之外,玩家可以自己制作Linux内核,在PCLinux系统内进行交叉编译应用程序,然后下载到乐高运行程序,这种方式比较灵活。更适合Linux开发者高级玩家。本次由于是第一次玩乐高,选用了BricxCC

3)安装putty
该软件网上比较多,安装也比较多,相信玩过Linux的人都对这个比较熟悉。可用于对EV3进行远程登录。


机器人动作控制

本小结用到的软件LEGO MINDSTORMS EV3 HomeEdition关于机器人的搭建,这里不在赘述啦,在LEGO MINDSTORMSEV3 Home Edition软件里面有操作指导,都是手工体力活,关于LEGO MINDSTORMS EV3 Home Edition的使用方法,也不在详述,乐高的这个软件设计得还算挺人性化的,一看就会。

来一张搭建好的图片:

QQ图片20151010100436.png

                        

随后就是图形化编程了,用图形化编程实现机器人动作控制,即:跟着信标移动,先来一个程序截图吧:

QQ图片20151010100505.png

QQ图片20151010100452.png

该程序我命名为FollowU,顾名思义,知道是什么意思了吧,上面的程序实现的功能:机器人的运动是控制是一个实时的负反馈过程,程序不断检测机器人与信标的夹角和与信标的距离,机器人根据与信标的夹角确定运动方向,根据与信标的距离,判断是否向前运行,最后的结果是:机器人将跟随信标一起移动,到信标不动,机器人运动到离信标指定距离开始向信标发起攻击(发射红色小球作为炮弹)。在控制机器人运动的同时,程序每隔5s采集一次传感器数据写入文件file1。该文件在EV3中为file.rtf.

这里也许大家有一个疑问,为什么将数据记录到文件而不是直接发送到OneNet,这个问题,在后面的叙述中会找到答案。



C++C程序和主程序通信

在阅读本论坛发布的相关帖子后,了解到一个非常好的设计复杂工程时的通信思路:主程序通过传感器进行原始数据采集以及结果的输出处理(比如合适的运动等),利用原生C++程序可以很好的完成更复杂的算法,比如复杂的搜索算法(解魔方、走迷宫等)、模式识别(声音、图形文字、人脸识别等),二者之间通过文件作为桥梁进行双向通信。这就是为什么在前面将采集到的数据写到文件里面的原因。

EV3中运行FollowU主程序,在file.rtf中数据格式如下:

QQ图片20151010100516.png

在这里不难发现,EV3在每次写入数据时,都将末尾加上换行符0x0D,对应’\r’,这里赢特别注意。在后面的C程序读取数据进行解析的时候,可以以此作为数据解析的依据。



数据上传至OneNet

在该部分实现对传感器采集到的数据上传到OneNetEV3可以通过Netgear WNA1100无线WIFI模块连接网络,首先要将Netgear WNA1100无线WIFI模块插入EV3USB口,在模块上启用WIFI,并连接到无线路由器。

然后运用BricxCC实现网络通信的编程,启动软件:

QQ图片20151010100524.png

这里保证PCEV3编程模块通过USB下载线连接,点击“OK”,具体BricxCC使用方法,前面也有帖子讲得很详细了,这里不在赘述。

要实现EV3OneNet互联,首先需要熟悉OneNet的接入协议,OneNet支持Restful APIEDPMODBUSMQTT等多种接入协议,相关接入协议的说明文档可登陆OneNet官方网站下载,网址:http://open.iot.10086.cn/,在OneNet官网上也有相关的接入协议示例程序供开发者参考。关于协议细节和接入原理,这里不在赘述。

本次实验使用EDP协议完成EV3OneNet平台的互联,下面看看设备连接和数据上传的主要C语言程序代码:

  1. #define SERVER_ADDR     "183.230.40.39"    //服务器地址
  2. #define SERVER_PORT     876                       //服务器端口
  3. static char buffer[512];
  4. static char send_buf[512];
  5. #define DEV_ID              "131988"                         //你的设备ID
  6. #define API_KEY             "gh9JNVcerTqg5todrP5bolCEN2kA" //你的APIkey
  7. #define FILENAME           "./SD_Card/FollowU/file1.rtf"

  8. int DoSend(int sockfd, char* buffer, unsigned int len)
  9. {
  10.     int32 total  = 0;
  11.     int32 n = 0;
  12.     while(len != total)
  13.     {
  14.         n = Send(sockfd,buffer + total,len - total,MSG_NOSIGNAL);
  15.         if(n <= 0)
  16.         {
  17.             fprintf(stderr, "ERROR writing to socket\n");
  18.             return n;
  19.         }
  20.         total += n;
  21.     }
  22.     return total;
  23. }

  24. void GetFileData(char *filename, long int data[])
  25. {
  26.     int i=0,j=0, fd, size;
  27.     char *delim = "\r", *p = NULL;
  28.     char buffer[20];

  29.     fd=open(filename, O_RDONLY);
  30.     size=read(fd,buffer,sizeof(buffer));
  31.     close(fd);

  32.     if(size > 0)
  33.     {
  34.         printf("Open file success!\nRead %d bytes data\n\n", size);
  35.     }
  36.     else
  37.     {
  38.         printf("open file error!\n");
  39.         return;
  40.     }

  41.     /*解析文件数据,此处以0x0D作为分隔符进行解析*/
  42.     data[0] = strtol(strtok(buffer, delim), NULL, 10);
  43.     data[1] = strtol(strtok(NULL, delim), NULL, 10);
  44.     p = strtok(NULL, delim);
  45.     sleep(1);
  46.     data[2] = strtol(p, NULL, 10);
  47.     sleep(1);

  48.     return;
  49. }

  50. int write_func(int arg)
  51. {
  52.     int sockfd = arg;
  53.     EdpPacket* send_pkg;
  54.     cJSON *save_json;
  55.     int32 ret = 0;
  56.     char text[25] = {0};
  57.     long int data[3] = {0};

  58.     /*检测数据文件是否存在,存在则读取,不存在就跳过*/
  59.     if(0 == access(FILENAME, 0))
  60.     {
  61.         /*从文件读取传感器数据并解析*/
  62.         GetFileData(FILENAME, data);

  63.         /*将数据封装成JSON格式*/
  64.         send_buf[0] = 0;
  65.         strcat(send_buf,"{"datastreams": [");

  66.         /*与信标角度*/
  67.         strcat(send_buf,"{"id": "degree",");
  68.         strcat(send_buf,""datapoints": [");
  69.         strcat(send_buf,"{");
  70.         sprintf(text,""value": "%d"", data[0]);
  71.         strcat(send_buf,text);
  72.         strcat(send_buf,"}]},");

  73.         /*与信标距离*/
  74.         strcat(send_buf,"{"id": "distance",");
  75.         strcat(send_buf,""datapoints": [");
  76.         strcat(send_buf,"{");
  77.         sprintf(text,""value": "%d"", data[1]);
  78.         strcat(send_buf,text);
  79.         strcat(send_buf,"}]},");

  80.         /*光照强度*/
  81.         strcat(send_buf,"{"id": "light",");
  82.         strcat(send_buf,""datapoints": [");
  83.         strcat(send_buf,"{");
  84.         sprintf(text,""value": "%d"", data[2]);
  85.         strcat(send_buf,text);
  86.         strcat(send_buf,"}]}");

  87.         strcat(send_buf,"]}");
  88.         save_json=cJSON_Parse(send_buf);
  89.         if(NULL == save_json)
  90.         {
  91.             return -1;
  92.         }

  93.         /*封装EDP包*/
  94.         send_pkg = PacketSavedataJson(DEV_ID, save_json);
  95.         if(NULL == send_pkg)
  96.         {
  97.             return -1;
  98.         }
  99.         cJSON_Delete(save_json);
  100.         
  101.         /*发送EDP数据包*/
  102.         ret = DoSend(sockfd, send_pkg->_data, send_pkg->_write_pos);
  103.         DeleteBuffer(&send_pkg);
  104.     }
  105.     else
  106.     {
  107.         send_pkg = PacketPing();
  108.         printf("send ping to server, bytes: %d\n", send_pkg->_write_pos);
  109.         DoSend(sockfd, send_pkg->_data, send_pkg->_write_pos);
  110.         DeleteBuffer(&send_pkg);
  111.     }

  112.     return ret;
  113. }

  114. int main()
  115. {
  116.     int sockfd, ret;
  117.     EdpPacket* send_pkg;

  118.     /* 创建一个与服务器的socket连接*/
  119.     sockfd = Open(SERVER_ADDR, SERVER_PORT);
  120.     if(sockfd < 0)
  121.     {
  122.         printf("Open error\r\n!");
  123.         return;
  124.     }

  125.     printf("Open OK!\r\n");
  126.     /*产生并发送设备连接请求的数据包 */
  127.     send_pkg = PacketConnect1(DEV_ID, API_KEY);
  128.     ret = DoSend(sockfd, send_pkg->_data, send_pkg->_write_pos);
  129.     DeleteBuffer(&send_pkg);

  130.     while(1)
  131.     {
  132.         /*产生并发送用户数据包 */
  133.         ret = write_func(sockfd);

  134.         if(ret < 0)
  135.         {
  136.             break;
  137.         }
  138.         sleep(1);
  139.     }

  140.     Close(sockfd);
  141. }
复制代码

这里只列出了简单的C实现流程代码,将代码通过BricxCC编译后下载到EV3,程序在EV3中的二进制文件名为test

原生C++程序天生有个缺点,无法通过EV3按钮直接选择,要想运行它,目前我只了解以下两种,一是bricxcc下面直接下载并运行,方法见这里,二是用telnet客户端软件登录进去运行,telnet又有两个办法,1是自制连接线的方法,见这里2是通过wifi连接进去,telnet时选择IP地址即可;不过一个具体的项目以上方法都不太妥当,魔方机器人给我们提供了另一种运行原生C++程序的方法。

首先要把刷成自制固件1.05M版,为什么呢?当我们运行主程序时,1.05M版会自动调用autorun.rtf程序,而autorun.rtf本质是一个脚本程序,内容如下:

  1. #!/bin/sh
  2. if [ -x test ]
  3. then
  4.   ./test &
  5. fi
复制代码

该脚本检测在当前目录下是否存在test可执行文件,如果存在,就在Linux后台运行该程序。

写好该脚本,可以通过BricxCCexplore功能将该脚本下载到EV3

QQ图片20151010100533.png

                              
这样,test程序就可以在系统启动后自动运行了,除此之外,可以通过Telnet远程登陆到EV3的内核,手动运行test程序。首先保证EV3WIFI已经启用并且已经联网,打开putty软件,配置如下:

QQ图片20151010100540.png
EV3通过WIFI联网后,主机IP地址可以在EV3的主机描述信息中查到,连接类型选择“telnet”,点击“打开”,进入登陆界面,输入用户名“root”,回车后进入EV3Linux系统命令行界面,如下图:

QQ图片20151010100547.png

通过Linux命令进入二进制文件“test”所在目录,后台运行该程序,如下图:

QQ图片20151010100552.png

程序一旦运行,就开始向OneNet传输数据了,我们可以在事先建立好的设备下和创建的设备应用下观察数据上传是否成功,设备下数据流更新情况如下图:


QQ图片20151010100557.png
添加的设备应用下数据流更新如下图:


QQ图片20151010100600.png

由此乐高与OneNet连接成功,通信正常。OneNet可以记录乐高机器人运动过程中的状态数据了。这里只上传了三个数据流,玩家可以根据自身需要依照上面的C代码添加多个数据流。

注意,OneNet平台上的设备创建和应用添加等相关操作在这里没有做详细介绍。如有不懂,请参考中国OneNet官方网站的OneNet连接教程。


如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
发表于 2015-10-10 10:47:33 | 显示全部楼层
厉害
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

 楼主| 发表于 2015-10-12 11:44:31 | 显示全部楼层

哈哈,初学者。
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2016-1-22 10:49:08 | 显示全部楼层
好nb的样子,机械的表示看的好乱
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2016-1-22 12:10:13 | 显示全部楼层
非常厉害
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2017-11-30 22:48:56 | 显示全部楼层
666
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 马上注册

本版积分规则

手机版|中文乐高 ( 桂ICP备13001575号-7 )

GMT+8, 2024-11-24 06:53 , Processed in 0.141720 second(s), 24 queries .

Powered by Discuz! X3.5

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表