【iOS与EV3混合机器人编程系列之六】iOS_WiFi_EV3_Library 剖析之发送命令给EV3

在上一篇文章中,我们已经知道了如何通过WiFi将iOS设备和EV3连接起来,那么下一步的工作就是从iOS设备中发送命令给EV3并接收EV3返回的数据。这也是本篇文章将告诉大家的。

首先要明确的一点是本开源代码库只封装了EV3直接命令(Direct Command),也就是无需在EV3上开发任何程序就能使用这些命令对EV3进行控制。

目前库中的API包含以下这些:

#pragma mark - EV3 Direct Command

// Scan or stop scan each port sensor condition and data on the ev3 brick

// 检测每个端口的数据

- (void)scanPorts;

- (void)stopScan;

// 清除所有命令

- (void)clearCommands;

#pragma mark - Motor Control Methods

// turn motor power at specified port and power

// 控制电机运转在特定的端口和特定的功率

- (void)turnMotorAtPort:(EV3OutputPort)port power:(int)power;

// 控制电机运转在特定的端口和特定的功率及特定的运转时间

- (void)turnMotorAtPort:(EV3OutputPort)port power:(int)power time:(NSTimeInterval)time;

// 控制电机运转在特定的端口和特定的功率及特定的转动角度

- (void)turnMotorAtPort:(EV3OutputPort)port power:(int)power degrees:(UInt32)degrees;

// 控制电机停止在特定的端口

- (void)stopMotorAtPort:(EV3OutputPort)port;

#pragma mark -  Sound Control Methods

// 播放音调在特定的音量特定的频率和特定的播放时间

- (void)playToneAtVolume:(int)volume frequency:(UInt16)frequency duration:(UInt16)duration;

// 播放音乐在特定的音量特定的文件及是否重复

- (void)playSoundAtVolume:(int)volume filename:(NSString *)filename repeat:(BOOL)repeat;

// 停止播放音乐

- (void)playSoundBrake;

#pragma mark - Image Control Methods

// 在EV3的显示屏上画图

- (void)drawImageAtColor:(EV3ScreenColor)color x:(UInt16)x y:(UInt16)y filename:(NSString *)filename;

// 在EV3的显示屏上显示文字

- (void)drawText:(NSString *)text color:(EV3ScreenColor)color x:(UInt16)x y:(UInt16)y;

// 在EV3的显示屏上画特定大小的窗口

- (void)drawFillWindowAtColor:(EV3ScreenColor)color y0:(UInt16)y0 y1:(UInt16)y1;

最重要的两部分就是读取端口数据以及控制电机转动,至于后面的声音和显示两部分不是特别重要,可以用iPhone取代。

那么,问题来了:如何创建并发送一个命令呢?

有以下几个步骤:

Step 1:根据直接命令协议创建特定命令的二进制数据

Step 2:将命令转化为特定的数据格式NSData

Step 3:通过TCP socket将命令数据发送出去。

下面我们就StepByStep地剖析实现它!

== Step1:创建直接命令 ==

这部分内容主要都在库中的EV3DirectCommand.m中实现。

为了让大家理解,我们先来了解一下EV3的直接命令协议!

首先还是摘录一下Direct Command的协议说明。这里完全EV3的源代码中的c_com.h文件中复制。关于EV3的源代码,大家可以在GitHub上下载,其网址为:https://github.com/mindboards/ev3sources

Beside running user programs the VM is able to execute direct commands from the Communication Module.

In fact direct commands are small programs that consists of regular byte codes and they are executed

in parallel with a running user program.\n

Special care MUST be taken when writing direct commands because the decision until now is NOT to

restrict the use of "dangerous" codes and constructions (loops in a direct command are allowed).

If a new direct command from the same source is going to be executed an actual running direct command is terminated.

Because of a small header objects are limited to one VMTHREAD only - SUBCALLs and BLOCKs is of

course not possible.\n

This header contains information about number of global variables (for response), number of local variables

and command size.

Direct commands that has data response can place the data in the global variable space. The global

variable space is equal to the communication response buffer. The composition of the direct command

defines at which offset the result is placed (global variable 0 is placed at offset 0 in the buffer).

Offset in the response buffer (global variables) must be aligned (float/32bits first and 8 bits last).

Direct Command Bytes:

,------,------,------,------,------,------,------,------,

|Byte 0|Byte 1|Byte 2|Byte 3|Byte 4|Byte 5|      |Byte n|

‘------‘------‘------‘------‘------‘------‘------‘------‘

Byte 0 – 1: Command size, Little Endian\n

Byte 2 – 3: Message counter, Little Endian\n

Byte 4:     Command type. see following defines   */

#define     DIRECT_COMMAND_REPLY          0x00    //  Direct command, reply required

#define     DIRECT_COMMAND_NO_REPLY       0x80    //  Direct command, reply not required

/*

Byte 5 - 6: Number of global and local variables (compressed).

Byte 6    Byte 5

76543210  76543210

--------  --------

llllllgg  gggggggg

gg  gggggggg  Global variables [0..MAX_COMMAND_GLOBALS]

llllll              Local variables  [0..MAX_COMMAND_LOCALS]

Byte 7 - n: Byte codes

Direct Command Response Bytes:

,------,------,------,------,------,------,------,------,

|Byte 0|Byte 1|Byte 2|Byte 3|      |      |      |Byte n|

‘------‘------‘------‘------‘------‘------‘------‘------‘

Byte 0 – 1: Reply size, Little Endian\n

Byte 2 – 3: Message counter, Little Endian\n

Byte 4:     Reply type. see following defines     */

#define     DIRECT_REPLY                  0x02    //  Direct command reply

#define     DIRECT_REPLY_ERROR            0x04    //  Direct command reply error

/*

Byte 5 - n: Response buffer (global variable values)

以上这些就是Direct Command的协议!
简单解释一下Direct Command就是前缀加上具体命令!所以搞清楚各种控制命令的格式是非常重要的!
比如我们要控制EV3的电机,还得需要知道控制电机的命令及组成方式!
更进一步地我们需要知道所有可以用的命令的格式!
从python-ev3.org 这个网站我们可以找到这些命令的格式!
当然从EV3的源代码也是可以找到的!在c_output.h文件中可以看到!

那么下面我们举一个最直接的例子来看看命令的二进制数据是怎么样的,这样大家就可以有一个直观的认识了。

== 举例:控制端口PortA的电机以功率50转动 ==

EV3中控制电机的命令主要有以下三个:

opOUTPUT_POWER(LAYER,NOS,SPEED)  // 设置电机的输出

Set power of the outputs

Dispatch status unchanged

Parameters:

(DATA8)LAYER - Chain layer number[0..3]

(DATA8)NOS - Output bit field[0x00..0x0F] output 1 to 4 (0x01, 0x02, 0x04, 0x08)

(DATA8)POWER - Power[-100..100]

opOUTPUT_START(LAYER,NOS)  // 启动电机

Starts the outputs

Dispatch status unchanged

Parameters:

(DATA8)LAYER - Chain Layer number[0..3]

(DATA8)NOS  -  Output bit field[0x00..0x0F]    端口

opOUTPUT_STOP(LAYER,NOS)  // 停止电机

Stop the outputs

Dispatch status unchanged

Parameters:

(DATA8)LAYER - Chain layer number[0..3]

(DATA8)NOS - Output bit field[0x00,0x0F]

(DATA8)BRAKE - Brake[0,1]  
经过研究,上面的DATA8格式其实就是unsigned char格式!
可以说有了这三个命令我们就能控制电机了。

要实现这个功能需要两个命令,一个是确定端口的输出,一个是启动输出。如果要用伪代码的形式表示就是:

opOUTPUT_POWER(0x00,PortA,50)

opOUTPUT_START(0x00,PortA)

先贴上这整个命令的二进制数据如下:

1100 0000 80 0000 a4 8100 8101 8132 a6 8100 8101

什么意思?

Byte 0,1: 0x1100  表示命令的长度,这里是小端对齐,所以其大小为0x0011,记住这边是16进制,所以0x0011大小为17,也就是说除了Byte0,1之外其他Byte也就是命令内容的长度为17Byte。理解了吗?大家可以数数看,是不是整个命令长度19Byte,扣除前两位,则命令内容为17Byte。

Byte 2,3: 0x0000 表示消息计数。这个的作用是什么呢?主要是为了分清楚接收的消息是对应发出的哪一个消息(命令)。举例说如果这里消息计数是1,那么返回的消息的消息计数也是1,这样就可以实现一一对应。在我们这边的应用中,我们不需要考虑对应问题,所以消息计数设置为0000就可以了。

Byte 4:0x80 命令类型(回复或者不回复)如果是0x00,则EV3接收到命令后要做出回复,如果是0x80,那么就不回复,这里我们因为是控制电机,无需返回数据,所以设置成0x80。

Byte 5,6 :0x0000 公共变量和私有变量的数量

这个在上面的协议中有解释,主要是用于确定返回数据的位置。那么这边我们无需返回任何数据,也就是无需任何变量,所以设置为0x0000。

接下来的Byte都是具体命令了。

每个命令都有专门的命令码,如下:

opOUTPUT_STOP               = 0xA3, //     00011

opOUTPUT_POWER              = 0xA4, //     00100

opOUTPUT_START              = 0xA6, //     00110

详见源代码中的bytecodes.h这个文件

接下来就是如何添加参数?

每个参数之前要先添加参数的长度!

typedef enum {

EV3ParameterSizeByte = 0x81,        // 1 byte

EV3ParameterSizeInt16 = 0x82,       // 2 bytes

EV3ParameterSizeInt32 = 0x83,      // 4 bytes

EV3ParameterSizeString = 0x84      // null-terminated string

}EV3ParameterSize;

不同长度的参数前面要添加的size参数不一样!也就是说比如我这边要添加power参数,那么之前就要先添加一个size参数,由于power参数是1 byte,所以添加0x81!

那么我们再回来看二进制数据:

Byte 7:0xa4 opOUTPUT_POWER的命令码

Byte 8,9,10,11,12,13: 0x8100 8101 8132 opOUTPUT_POWER对应的三个参数,0x8100表示layer,其值为0,0x8101表示port,其值01为PortA,0x8132表示功率,其值为0x32 = 50.

Byte 14:0xa6  opOUTPUT_START的命令码

Byte 15,16,17,18:0x8100 8101 opOUTPUT_START对应的两个参数,0x8100表示layer,其值为0,0x8101表示port,其值01为PortA。

我们的代码库就是要将这些二进制数据创建起来,具体的实现涉及大量的二进制移位操作,大家可以自己查看文件进行分析,这里不再细说。

这个功能对应的API是

+ (NSData *)turnMotorAtPort:(EV3OutputPort)port power:(int)power

{

EV3DirectCommander *command = [[EV3DirectCommander alloc] initWithCommandType:EV3CommandTypeDirectNoReply globalSize:0 localSize:0];

[command addOperationCode:EV3OperationOutputPower];

[command addParameterWithInt8:0];

[command addParameterWithInt8:port];

if (abs(power) > 100) {

[command addParameterWithInt8:100];

} else {

[command addParameterWithInt8:(Byte)power];

}

[command addOperationCode:EV3OperationOutputStart];

[command addParameterWithInt8:0];

[command addParameterWithInt8:port];

return [command assembledCommandData];

}

具体实现时就是编写如下的代码:

[self turnMotorAtPort:EV3OutputPortA power:50];

可以说其他的命令组成方法道理都是一样的。

大家可以自己分析一下获取传感器数据的命令是如何组成的。

== Step 2:转换为NSData数据 ==

这里在EV3DirectComand.m文件通过一个方法实现:

[NSData dataWithBytes:buffer length:cursor];

仅仅是格式转换,大家即使不理解也没有关系。

== Step 3:发送命令 ==

这个工作在EV3Device.m文件中实现,也很简单。

下面是控制电机的发送命令代码:

- (void)turnMotorAtPort:(EV3OutputPort)port power:(int)power

{

// 封装命令数据

NSData *data = [EV3DirectCommander turnMotorAtPort:port power:power];

// 发送命令

[self.tcpSocket writeData:data withTimeout:-1 tag:MESSAGE_NO_REPLY];

}

大家看了源代码应该就可以明白。这里不再多讲。对于TCP方面不了解的童鞋请查看之前的文章。

好啦,关于如何创建并发送Direct Command命令就介绍到这里。可以说,如果大家理解到这里,那么对整个库的编写也就基本理解了。之后我们就不再讲解这个代码库的问题了。

我们将开始一个真正的项目:用iOS来体感控制EV3 坦克!

大家准备好了吗?

敬请期待下一篇文章!

【本文为原创文章,版权所有,转载请注明出处!blog.csdn.net/songrotek

[email protected] 谢谢!】

时间: 2024-10-24 15:28:40

【iOS与EV3混合机器人编程系列之六】iOS_WiFi_EV3_Library 剖析之发送命令给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网络中

【iOS与EV3混合机器人编程系列之二】工欲善其事,必先利其器(准备篇)

在上一篇文章中,我们论述了iOS与EV3结合后机器人开发的无限可能, 那么,大家要不要一起来Hacking一把呢? 为了能够完整地完成我接下来我讲的项目,我们需要做以下准备: 1.一台Mac运行MAC OS X 10.9.3以上的操作系统. 2.Xcode6.这是iOS在Mac上的开发工具.我们将使用Xcode来进行所有的项目程序的编写 3.一两个iOS设备,iPhone或iPad都行.实际上大家最好有两个iOS设备,因为最后的项目中iPhone将和EV3机器人放在一起,而用另一个iOS设备来查

【iOS与EV3混合机器人编程系列之五】iOS_WiFi_EV3_Library 剖析之连接EV3

在上一篇文章中,我们讲解了如何用开源代码库CocoaAsyncSocket来实现iOS上的UDP和TCP数据通信.那么在本文中,我们将介绍在CocoaAsyncSocket的基础如何使用UDP和TCP连接EV3的机制. 之所以我们能够通过无线连接EV3,根本原因在于EV3的源代码内建了一套无线连接通信的机制. 这套机制是这样的: 1)EV3在连接到无线网络后,就不断地从3015端口发送UDP数据,数据的格式如下: Serial-Number: 0016533f0c1ePort: 5555Name

【iOS与EV3混合机器人编程系列之一】iOS要干嘛?EV3可以更酷!

乐高Mindstorm EV3智能机器人(以下简称EV3)自从在2013年的CES(Consumer Electronics Show美国消费电子展)上展出之后,就吸引了全球广大机器人爱好者的眼球!EV3相比其上一代机器人NXT最大的提升就在于其硬件上.除了更强大的ARM处理器,并加载了Linux操作系统之外,EV3还配备了蓝牙,支持外接USB,外接WiFi.因为有了这么多的硬件提升,EV3最酷的特性在于EV3支持iOS设备!!!这使得我们可以使用iOS设备比如iPhone来控制EV3!乐高官方

【iOS与EV3混合机器人编程系列之四】iOS_WiFi_EV3_Library 剖析之一:WiFi UDP和TCP

在上一篇文章中,我们通过编写EV3 Port Viewer项目实现了iOS监测EV3的实时端口数据.程序最核心的部分就是我们的开源代码库iOS_WiFi_EV3_Library.那么,在本文中,我们将详细介绍我们这个库的编写.为了完成这个库,本人参考了网上很多资料,主要包括EV3的源代码,win版本的代码库以及Monobrick相关以及网上的各种资料,在此就不一一列举了.由于水平有限,本代码库还存在各种问题,望使用的读者见谅.大家也可以在这个基础之上自己进行改造完善. 为了详细说明代码库的实现,

【iOS与EV3混合机器人编程系列之四】iOS_WiFi_EV3_Library 剖析之中的一个:WiFi UDP和TCP

在上一篇文章中.我们通过编写EV3 Port Viewer项目实现了iOS监測EV3的实时端口数据. 程序最核心的部分就是我们的开源码库iOS_WiFi_EV3_Library. 那么,在本文中,我们将具体介绍我们这个库的编写.为了完毕这个库,本人參考了网上许多资料,主要包括EV3的源码,win版本号的代码库以及Monobrick相关以及网上的各种资料,在此就不一一列举了. 因为水平有限,本代码库还存在各种问题,望使用的读者见谅. 大家也能够在这个基础之上自己进行改造完好. 为了具体说明代码库的

【iOS与EV3混合机器人编程系列之7】通过蓝牙控制EV3

1 前言 在这个系列之前的博客中,我研究觉得在iOS未越狱的情况下,无法使用蓝牙来控制EV3,编写类似Commander的程序.但,最近和网友的研究发现,通过External Accessory 来实现蓝牙的传输比想象的简单.MFI协议的问题比想象的容易很多,关键在于我们可以获取EV3的MFI协议字符串.接下来让我们看看是怎么实现的. 2 具体代码实现 首先Apple官方有个关于External Accessory的demo 叫EAdemo,大家可以下下来,然后在plist文件中改一下协议字符串

【iOS与EV3混合机器人编程一系列五个】iOS_WiFi_EV3_Library 解剖连接EV3

在上一篇文章中.我们解说了怎样用开源码库CocoaAsyncSocket来实现iOS上的UDP和TCP数据通信.那么在本文中.我们将介绍在CocoaAsyncSocket的基础怎样使用UDP和TCP连接EV3的机制. 之所以我们能够通过无线连接EV3,根本原因在于EV3的源码内建了一套无线连接通信的机制. 这套机制是这种: 1)EV3在连接到无线网络后,就不断地从3015port发送UDP数据.数据的格式例如以下: Serial-Number: 0016533f0c1ePort: 5555Nam

.NET 4 并行(多核)编程系列之一入门介绍

本系列文章将会对.NET 4中的并行编程技术(也称之为多核编程技术)以及应用作全面的介绍. 本篇文章的议题如下:  1. 并行编程和多线程编程的区别.  2. 并行编程技术的利弊  3. 何时采用并行编程 系列文章链接: .NET 4 并行(多核)编程系列之一入门介绍 .NET 4 并行(多核)编程系列之二 从Task开始 .NET 4 并行(多核)编程系列之三 从Task的取消 .NET 4 并行(多核)编程系列之四 Task的休眠 .NET 并行(多核)编程系列之五 Task执行和异常处理