CocoaAsyncSocket (二)

本文介绍了一个iOS与EV3机器人混合编程的方案,利用WiFiUDP和TCP技术实现iOS设备与EV3机器人的交互。文章详细阐述了如何通过GCDAsyncSocket库建立TCP和UDP连接,包括连接流程、数据收发及设备管理等方面的内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考了一篇机器人控制的文章,总结一下:

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

其中

1)拥有一个连接界面来搜索并连接WiFi局域网内的EV3设备。
2)实时接收EV3的端口数据并且能非常方便地对数据进行处理。
3)能够发送直接命令(Direct Command)到EV3实现对EV3的实时控制。

和路由器的直连需求是一样的

我们只需要使用库中的四个文件:
GCDAsyncSocket.h // 实现TCP传输
GCDAsyncSocket.m
GCDAsyncUdpSocket.h // 实现UDP传输
GCDAsyncUdpSocket.m

1.UDP的使用

Step 1:创建实例instance
GCDAsyncUdpSocket
Step 2:连接UDP到特定端口
这里我们使用这个类中的方法:

1)连接API:bindToPort:error:
2)接收数据API:beginReceiving:
3)关闭API:close

Step 3:接收UDP数据
通过delegate方法
UDPsocket已收到数据
Step 4:发送UDP数据

2、TCP的使用

Step 1:创建实例instance GCDAsyncSocket类
Step 2:连接TCP
Step 3:发送TCP数据
Step 4:接收数据
Step 5:数据处理 通过Delegate方法实现
// TCP socket已连接
// TCP socket已断开
// TCP socket已写入数据
支持多机连接,因此我们在每个EV3Device中处理数据。从EV3那边接收的任何数据包含端口数据这是在这里进行处理。
这里可以做一些设备相关的信息存储,比较灵活。
// TCP socket已发送数据

这篇文章也提供了源码:

songrotek/iOS_WiFi_EV3_Library

看一下资源结构

uml类图

类图的连接地址

EV3CommandDefinitions.h

这个是枚举的.h文件,将16进制的枚举全部写进来,typedef enum {
EV3ParameterSizeByte = 0x81, // 1 byte
EV3ParameterSizeInt16 = 0x82, // 2 bytes
EV3ParameterSizeInt32 = 0x83, // 4 bytes
EV3ParameterSizeString = 0x84 // null-terminated string
}EV3ParameterSize;

类似上文,这样写的时候引入枚举就能够进行16进制的拼接转化等就像这样`
- (void)addParameterWithInt8:(Byte)parameter
{
buffer[cursor] = EV3ParameterSizeByte;
cursor++;
buffer[cursor] = parameter;
cursor++;

EV3Device.h
EV3Device.m

这个是设备信息模型,会由EV3WifiManager.h 管理初始化和信息处理,连接到某个设备,以及EV3WifiManager管理多台设备的设备列表
设备来管理TCP的操作,因为每台设备才会和业务相关,因此将业务有关的控制机器人、路由器的操作都在这里 类内部只用了[self.tcpSocket writeData:data withTimeout:-1 tag:MESSAGE_CLEAR]; 这个方法

//EV3WifiManager中对设备的操作
// 连接或者断开ev3到iOS。连接之后,就可以发送直接命令了。
- (void)connectTCPSocketWithDevice:(EV3Device *)device;
 // udp接收到数据就要建立一个设备,给设备添加一个tcp的连接
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data
{
 NSString *msg = [[NSString alloc]     initWithData:data  encoding:NSUTF8StringEncoding];
    if (msg)
    {
 NSString *serialNumber = [msg substringWithRange:NSMakeRange(14, 12)];
 //主机mac地址
 NSString *host = [GCDAsyncUdpSocket hostFromAddress:address];
 //取出device设备数据
 EV3Device *device = [self.devices objectForKey:host];
 //如果没有设备就信息
 if (!device && host.length < 20) {
  //初始化一个设备
  EV3Device *aDevice = [[EV3Device alloc] initWithSerialNumber:serialNumber address:host tag:self.devices.count isConnected:NO];
 // 启动 TCP/IP socket
 dispatch_queue_t tcpSocketQueue = dispatch_queue_create("com.manmanlai.tcpSocketQueue", DISPATCH_QUEUE_CONCURRENT);
 GCDAsyncSocket *tcpSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:tcpSocketQueue];
 //把这个tcpSocket 给设备的tcpSocket对象
aDevice.tcpSocket = tcpSocket;
// 将设备添加到设备列表里
[self.devices setObject:aDevice forKey:aDevice.address];
//发送消息去刷新列表
if ([self.delegate respondsToSelector:@selector(updateView)]) {
 dispatch_async(dispatch_get_main_queue(), ^{
                    [self.delegate updateView];
                });
            }
        }
    }
    else
    {
        NSLog(@"Error converting received data into UTF-8 String");
    }
//udp发送数据 --------
[self.udpSocket sendData:data toAddress:address withTimeout:-1 tag:0];
}
//tcp发送数据 --------
#pragma mark - GCDAsyncSocket
- (void)connectTCPSocketWithDevice:(EV3Device *)device
{   //获取到设备socket并且建立连接时在这里,这样设备里面可以克控制这个socket ,这些代理方法也能够控制socket,这样变得两个类都能对tcpsocket进行控制,可能需要把当前的这些操作打包给device类来做,来减少依赖关系。
//作者在这里这样使用应该是为了同时能够管理udp连接和tcp设备管理所有的socket问题,
   //获取到tcp socket 
    GCDAsyncSocket *tcpSocket = device.tcpSocket;
    // 建立连接
    NSError *error = nil;
    if (![tcpSocket connectToHost:device.address
                                onPort:5555
                                 error:&error])
    {
        NSLog(@"Error connecting: %@", error);

    } else {
        NSLog(@"Connected");
        // write data
        NSLog(@"writing...");
        NSString *unlockMsg = [NSString stringWithFormat:@"GET /target?sn=%@ VMTP1.0 Protocol: EV3",device.serialNumber];
        NSData *unlockData = [unlockMsg dataUsingEncoding:NSUTF8StringEncoding];
        //写数据
        [tcpSocket writeData:unlockData withTimeout:-1 tag:MESSAGE_UNLOCK];
        //读取数据
        [tcpSocket readDataWithTimeout:-1 tag:MESSAGE_UNLOCK];  
    }
}
- (void)disconnectTCPSocketWithDevice:(EV3Device *)device
{
    //设备停止扫描
    [device stopScan];
    //设备断开连接
    [device.tcpSocket disconnect];
    //设备列表移除这个设备地址-----我的app需求可能不太一样这里
    [self.devices removeObjectForKey:device.address];
    //刷新界面
    if ([self.delegate respondsToSelector:@selector(updateView)]) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.delegate updateView];
        });
    }
}

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{ 
    NSString *host = sock.connectedHost;
    EV3Device *device = [self.devices objectForKey:host];
    [device handleReceivedData:data withTag:tag];
}

EV3DirectCommander.h
EV3DirectCommander.m

16进制操作的类,所有16进制的操作都会在这里,参数的发送都在这里,EV3Device设备管理了业务的收发,而收发消息的过程中,都需要进行16进制的拼接,就引入了这个类,

使用的时候就一直调用+方法,具体例子
NSData *data = [EV3DirectCommander turnMotorAtPort:port power:power];
[self.tcpSocket writeData:data withTimeout:-1 tag:MESSAGE_NO_REPLY];
至于内部---调用初始化方法后添加必要参数返回即可
+ (NSData *)playToneWithVolume:(int)volume frequency:(UInt16)frequency duration:(UInt16)duration
{
EV3DirectCommander *command = [[EV3DirectCommander alloc] initWithCommandType:EV3CommandTypeDirectNoReply globalSize:0 localSize:0];
[command addOperationCode:EV3OperationSoundTone];
[command addParameterWithInt8:(Byte)volume];
[command addParameterWithInt16:frequency];
[command addParameterWithInt16:duration];
return [command assembledCommandData];
}

宏定义ios向机器人发送信息的类型

define Ev3WifiFit_EV3MessageType_h
define MESSAGE_UNLOCK 1 // 发送ev3解锁信息
define MESSAGE_NO_REPLY 11
define MESSAGE_GET_SENSOR_TYPE_AND_MODE 12
define MESSAGE_READ_DATA 13
define MESSAGE_SCAN_PORTS 14
define MESSAGE_SCAN_SENSOR_TYPE_AND_MODE 15
define MESSAGE_SCAN_SENSOR_DATA 16
define MESSAGE_CLEAR 17
define MESSAGE_UNDEFINE 0
具体使用:
[self.tcpSocket writeData:data withTimeout:-1 tag:MESSAGE_NO_REPLY];

EV3Sensor.h
EV3Sensor.m

Device设备里的元器件,组成部分,每个设备有8个元器件

EV3WifiBrowserViewController.h
EV3WifiBrowserViewController.m

这个是简单的界面显示控制,对不同状态的连接情况显示不同的图表TableView

EV3WifiManager.h
EV3WifiManager.m

这个是单例管理类 ,添加到控制器中,遵循其代理方法,控制udp管理和设备tcp的管理

// 具体使用 
// init ev3WifiManager
self.ev3WifiManager = [EV3WifiManager sharedInstance];
self.ev3WifiManager.delegate = self;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ev3DeviceConnected:) name:EV3DeviceConnectedNotification object:nil];

[self.ev3WifiManager startUdpSocket];
//[self.udpSocket close];
[self.ev3WifiManager stopUdpSocket];

//devices count info
NSUInteger count = [self.ev3WifiManager.devices  count];
EV3Device *device = [[self.ev3WifiManager.devices allValues] objectAtIndex:indexPath.row];

//connect device
[self.ev3WifiManager connectTCPSocketWithDevice:device];
[self.ev3WifiManager disconnectTCPSocketWithDevice:device]; 
#pragma mark - ev3 wifi manager delegate
- (void)updateView
{
    [self update];
}

GCDAsyncSocket.h
GCDAsyncSocket.m
GCDAsyncUdpSocket.h
GCDAsyncUdpSocket.m

是socket TCP 和UDP 需要包含的第三方库CocoaAsyncSocket

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值