首页 > 代码库 > 【iOS与EV3混合机器人编程系列之三】编写EV3 Port Viewer 应用监测EV3端口数据

【iOS与EV3混合机器人编程系列之三】编写EV3 Port Viewer 应用监测EV3端口数据

在前两篇文章中,我们对iOS与EV3混合机器人编程做了一个基本的设想,并且介绍了要完成项目所需的软硬件准备和知识准备。

那么在今天这一篇文章中,我们将直接真正开始项目实践。


==第一个项目: EV3 Port Viewer==


项目目的:在iOS设备上通过WiFi连接EV3并且读取EV3每个端口的数据。


大家可以一周之后在App Store上搜索EV3 Port Viewer,那么我已经做了一个范例App发布了,正在审核中



应用的基本使用要求:将EV3和iPhone同时连接到同一个WiFi网络中。对于EV3,必须使用NetGear WNA1100 WiFi Dongle。网卡的使用非常简单,只有插在EV3 Brick的USB接口上就能使用了

这里不得不说明的是:使用iOS7及以上版本的iPhone,EV3无法直接连接到iPhone的热点上!!


可能原因:在iOS7之后,iPhone的热点只支持WPA2 PSK的加密格式,而NetGear WNA1100在EV3上则只能使用WPA2或None。目前我还没有找到有效的解决办法,大家可以一起研究解决。这个问题从本质上看严重影响了iOS与EV3混合机器人的体验!这使得我们不得不单独再弄一个路由器,很麻烦。


==开始==


我已经将iOS与EV3连接及控制的程序编写成库分享到GitHub上,并且本项目的程序也直接分享了。


https://github.com/songrotek/iOS_WiFi_EV3_Library.git

https://github.com/songrotek/EV3PortViewer.git


大家可以先下下来,然后跟着本教程一步一步编写这个项目。在这里我将会一步一步地剖析我编写的这个代码库的实现原理。与此同时,考虑到阅读本文的读者可能大都不了解iOS开发,因此本文将非常详细的介绍每一个开发步骤!


==Step 1:建立项目==

打开Xcode,新建一个项目,选择Single View Application,点击Next。





将项目命名为EV3PortViewer。Company Identifier选择你们自己的开发者账号里申请的App ID。对于没有开发者账号的童鞋,那么不要考虑这个。如果大家想要真机测试,那么有两种选择,一个是花99美元申请一个账号,一个是在淘宝上购买一个真机测试的证书。虽然说在淘宝上这种方式不怎么好,但对于刚刚开始研究iOS开发的童鞋,不失为一个省钱的方式。

接下来Class Prefix留空,然后Device选择iPhone。这里不使用iPad只是因为iPad太大麻烦。之后或许会考虑出个iPad版本。

设置好之后,点击Next创建。


==Step 2:添加代码库==

大家下载我的代码库之后,将其添加进来。方法就是点击项目右键,点击 Add Files to “EV3PortViewer”…,如下图所示:



文件夹名称为iOS_WiFi_EV3_Library,添加进来后如下图所示:


文件比较多,大家先不用管,之后会讲解。


==Step 3:更改StoryBoard==

这里我们要在主视图中使用Table的方式来显示每一个EV3端口的传感器的数据,因此我们要新建一个TableViewController然后把原来的ViewController替换掉。


点击Main.storyboard,点击中间的viewController,删除掉。



单击右侧工具栏里面的TableViewController,然后按住拉进storyboard中。


接下来为了添加连接按钮,我们需要将TableViewController嵌入到NavigationController中。

在已经选择TableViewController之后,点击Editor->Embed in->Navigation Controller。



接下来拉进来两个Bar Button Item到Navigation Bar当中,左右两边各一个,分别命名为Connect和Help。然后双击Navigation Bar中间,将Bar命名为EV3,如下图所示:



==Step 4:创建Table View Controller==

新建一个文件,选择Objective-C class。





将Class命名为Ev3TableViewController,并且将Subclass of设置为UITableViewController。



然后保存在EV3PortViewer文件夹中。

Ev3TableViewController新建完成后,点击storyboard,将里面的tableViewController的Class设置为Ev3TableViewController。



==Step 5:创建Table View Cell==

由于我们要在每一个Table View Cell中显示传感器的图片,还有数据,因此我们需要自定义一个Table View Cell。同前面的新建文件的方法一样。新建一个文件命名为Ev3Cell,然后Subclass Of 选择UITableViewCell。



Ev3Cell创建好之后,打开storyboard,点击Ev3TableViewController中的tableviewCell,将其类型改为Ev3Cell。



==Step 6:连接Button,定义方法Method==

将storyboard中的按钮Button Connect通过按住Ctrl左键点击拉到Ev3TableViewController文件中来定义Action。



OK,一切非代码工作完成。接下来我们开始进入编程阶段,来实现每个功能。


==Step 7:实现连接功能==


在Ev3TableViewController.m中最上面添加要使用的代码库的文件:

#import "EV3WifiBrowserViewController.h"
#import “EV3WifiManager.h"

说明一下:EV3WifiBrowserViewController专门用来查看和连接EV3设备,而EV3WifiManager用来管理与EV3的通信,包含接收数据和发送控制命令。


接下来在connectOrDisconnect中添加如下代码:

- (IBAction)connectOrDisconnect:(id)sender
{
    EV3WifiBrowserViewController *browserViewController = [[EV3WifiBrowserViewController alloc] init];
    
    [self presentViewController:browserViewController animated:YES completion:nil];
}


Ok,只要这么简单的代码就行了,现在我们已经可以连接EV3了!!

先来测试一下看看!


在iPhone和EV3都连接到WiFi之后,启动App,点击Connect按键。

大家可以看到,App自动搜索EV3,并直接显示出EV3的ip地址,点击地址,正常情况下,EV3就直接连接了。

 

连接上之后,其实App就已经开始接收从EV3那边传过来的各个端口的数据了,不过我们现在还看不到。

要断开连接也很简单,再点击一下已连接的ip地址,就会弹出是否断开连接的提示。


==Step 8: 显示端口数据==


这是这个App的功能所在,就是类似于在EV3上直接可以看到的Port View,只不过我们把这个功能搬到了iPhone上。


由于我对于这个代码库的封装使得现在要获取端口数据变得易如反掌。大家马上可以看到。这个程序最大的问题可能在于这个Ev3TableViewController的编写上。


对于对iOS开发完全不了解的童鞋,这里我也无法讲得再细使大家都能理解。建议不了解的童鞋还是先看看iOS开发的基础教程。


下面介绍每一步的实现方法。


Step 7.1 :编写 Ev3Cell

我们要显示每个端口的信息,包含以下几个方面:

1)端口连接的传感器,上图片

2)端口的名称

3)端口连接的传感器的名称

4)端口连接的传感器发送过来的实时数据


因此,打开Ev3Cell.h,添加如下代码:

@property (nonatomic,strong) UIImageView *sensorImage;
@property (nonatomic,strong) UILabel *portLabel;
@property (nonatomic,strong) UILabel *nameLabel;
@property (nonatomic,strong) UILabel *dataLabel;



接下来,打开Ev3Cell.h,添加如下代码到初始化Method中:

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        // Initialization code
        
        self.frame = CGRectMake(0, 0, 320, 120);
        
        self.sensorImage = [[UIImageView alloc] initWithFrame:CGRectMake(10, 10, 100, 100)];

        self.portLabel = [[UILabel alloc] initWithFrame:CGRectMake(140, 15, 180, 20)];
        
        self.nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(140, 50, 180, 20)];
        
        self.dataLabel = [[UILabel alloc] initWithFrame:CGRectMake(140, 85, 180, 20)];
        
        
        [self addSubview:self.sensorImage];
        [self addSubview:self.portLabel];
        [self addSubview:self.nameLabel];
        [self addSubview:self.dataLabel];
        
        
    }
    return self;
}



这里我们采用纯手写的方式来编写Ev3Cell,我们还可以使用storyboard或xib通过图形界面来设置,这里不多讲。


OK,我们编写好了Ev3Cell,接下来是Ev3TableViewController


Step 7.2 完善Ev3TableViewController


1)打开Ev3TableViewController.m,添加头文件

#import "EV3WifiBrowserViewController.h"
#import "EV3WifiManager.h"
#import “Ev3Cell.h"


2)添加property


@property (nonatomic,strong) EV3WifiManager *ev3WifiManager;
@property (nonatomic,strong) EV3Device *device;


3)添加初始化代码在ViewDidLoad:方法中

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.ev3WifiManager = [EV3WifiManager sharedInstance];
    
    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(refreshData) userInfo:nil repeats:YES];
}



这里因为考虑到我们需要实时更新数据,所以弄了一个定时器NSTimer,让其每0.1s更新一次。


4)添加refreshData方法

- (void)refreshData
{
    self.device = self.ev3WifiManager.devices.allValues.lastObject;
    [self.tableView reloadData];
}



5)更改tableView的设置

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    if (self.device) {
        return 8;
    } else return 1;
    
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 120;
}


6)更改Cell的内容

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    Ev3Cell *cell = [[Ev3Cell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
    cell.userInteractionEnabled = NO;
    
    if (self.device) {
        
        switch (indexPath.row) {
            case 0:
                cell.portLabel.text = @"PORTA";
                cell.nameLabel.text = self.device.sensorPortA.typeString;
                cell.imageView.image = self.device.sensorPortA.image;
                cell.dataLabel.text = [NSString stringWithFormat:@"Raw Data:%d",self.device.sensorPortA.data];
                break;
            case 1:
                cell.portLabel.text = @"PORTB";
                cell.nameLabel.text = self.device.sensorPortB.typeString;
                cell.imageView.image = self.device.sensorPortB.image;
                cell.dataLabel.text = [NSString stringWithFormat:@"Raw Data:%d",self.device.sensorPortB.data];
                break;
            case 2:
                cell.portLabel.text = @"PORTC";
                cell.nameLabel.text = self.device.sensorPortC.typeString;
                cell.imageView.image = self.device.sensorPortC.image;
                cell.dataLabel.text = [NSString stringWithFormat:@"Raw Data:%d",self.device.sensorPortC.data];
                break;
            case 3:
                cell.portLabel.text = @"PORTD";
                cell.nameLabel.text = self.device.sensorPortD.typeString;
                cell.imageView.image = self.device.sensorPortD.image;
                cell.dataLabel.text = [NSString stringWithFormat:@"Raw Data:%d",self.device.sensorPortD.data];
                break;
            case 4:
                cell.portLabel.text = @"PORT1";
                cell.nameLabel.text = self.device.sensorPort1.typeString;
                cell.imageView.image = self.device.sensorPort1.image;
                cell.dataLabel.text = [NSString stringWithFormat:@"Raw Data:%d",self.device.sensorPort1.data];
                break;
            case 5:
                cell.portLabel.text = @"PORT2";
                cell.nameLabel.text = self.device.sensorPort2.typeString;
                cell.imageView.image = self.device.sensorPort2.image;
                cell.dataLabel.text = [NSString stringWithFormat:@"Raw Data:%d",self.device.sensorPort2.data];
                break;
            case 6:
                cell.portLabel.text = @"PORT3";
                cell.nameLabel.text = self.device.sensorPort3.typeString;
                cell.imageView.image = self.device.sensorPort3.image;
                cell.dataLabel.text = [NSString stringWithFormat:@"Raw Data:%d",self.device.sensorPort3.data];
                break;
            case 7:
                cell.portLabel.text = @"PORT4";
                cell.nameLabel.text = self.device.sensorPort4.typeString;
                cell.imageView.image = self.device.sensorPort4.image;
                cell.dataLabel.text = [NSString stringWithFormat:@"Raw Data:%d",self.device.sensorPort4.data];
                break;
                
            default:
                break;
        }

    } else {
        cell.textLabel.text = @"Waiting For Connection Of EV3!";
        cell.textLabel.textAlignment = NSTextAlignmentCenter;
    }
    
    
    return cell;
}



一切OK!


解释一下:

1)self.device = self.ev3WifiManager.devices.allValues.lastObject;

通过这一行代码来获取连接的EV3设备。之所以这么复杂,是因为我编写的这个代码库其实支持多机连接,每一个连接到iPhone的EV3都存在ev3WifiManager的devices中。大家具体看代码可以知道。devices我采用dictionary而不是array

2)获取传感器数据。

很简单:

device.sensorPortA

通过这个我们可以获取device对应的端口PortA连接的传感器。

每个传感器包含以下属性:

@property (nonatomic,assign) EV3SensorType type; //类型
@property (nonatomic,strong) NSString *typeString; // 类型字符串
@property (nonatomic,strong) UIImage *image; //传感器图片
@property (nonatomic,assign) int mode; // 传感器工作模式
@property (nonatomic,assign) short data; // 传感器数据


基本上传感器数据的刷新频率是60Hz左右。由于我认为只要一种传感器模式就够了,比如电机的转角,我们只需要角度制而不需要幅度值,因此这边的mode始终是0.


从上面的代码大家应该可以很清楚的看到获取传感器的方法。


== Ev3 Port Viewer效果==

这就是我们完成的App的效果!是不是很简单也很酷呢??


具体关于开源代码库的实现原理,敬请期待下一篇文章讲述!大家也可以先自己研究看看!


【本文为原创文章,版权所有,转载请注明出处,谢谢! songrotek@qq.com】