十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
最近在做一款外设,搭配一款App,App中实现很多功能,集成了一套语音识别SDK,通过外设给App传输音频,通过SDK解析出具体的指令,去执行App中相关的功能。整体功能是这样,在iOS系统中,以上功能中有几个难点,很难解决或者是无法解决,只能避开。笼统的分以下几个点:
站在用户的角度思考问题,与客户深入沟通,找到宜春网站设计与宜春网站推广的解决方案,凭借多年的经验,让设计与互联网技术结合,创造个性化、用户体验好的作品,建站类型包括:网站建设、做网站、企业官网、英文网站、手机端网站、网站推广、域名申请、雅安服务器托管、企业邮箱。业务覆盖宜春地区。
1).App在后台,外设能否拉起App
2).App被拉起之后,能否进行执行语音指令
3).App在后台,能否接收音频流,执行语音指令
上面这个问题是整个流程中最头疼的问题
1.首先要定义外设的形态,这里的外设可以是耳机,稍微变形一下可以是车载,录音笔,音响等。这里的外设就用耳机来代表。外设传输音频到App,无线场景下大部分都是采用蓝牙,蓝牙又分MFI认证的蓝牙设备和ble设备。
首先要说的是MFi认证的蓝牙设备,这类设备使用iAP协议同iPhone, iPad and iPod进行数据交互,传输性能好,稳定,但是成本比较大,设备需要苹果的官方检测很麻烦。当时查了一些资料,MFi认证的设备似乎是能解决上面说的几个问题。如图1所示,MFi认证的设备跟App商定好协议之后就能通过语音拉起App,但是这里有个坑,那段话我找不到了,意思是如果是usb模式的外设是可以直接拉起App,但是如果非usb模式的,拉起之前会出一个弹框,看图1 我们都以为这个alert是可选的,其实并不是,在某些模式下,必须是带alert的,如果拉起App需要Alert,让用户去确认,那也就没必要通过语音唤醒了,直接点击App好了,所以这条路基本就不考虑了。
但是外设是可以拉起Siri的,这个是固件需要支持的。这又为App打开了一条路,外设可以通过Siri打开App,这条路可行。但是Siri Shortcuts是iOS12之后才支持的。考虑低版本暂时没有想到好的解决方案。
苹果针对iap设备有一个background mode, 如图2 中的External accessory communication。设置了这个mode之后,在用户不主动杀死App,或者是系统不因为内存占用太高而杀死App,App理论上会一直在后台“活着”的,针对这个“活着”又是另一个话题了,这里的活着是假活的,也就是只能接收到来自外设传输的信息,所有其他的网络请求啥的都会被系统停止,在我的另一篇文章 iOS 后台机制探索 中讲到了这个,所以在后台这个模式只是给我们提供了一个入口,而且在接收到外设信息之后,系统只有10几秒的时候给App进行处理,具体多少秒,我没有实测,我在接收到消息之后,重连App中的socket连接进行数据发送这些操作都是能正常完成的。
所以总结以下,在通过MFi认证,使用iap进行数据交互的设备在我们这边的产品来看,能拉起App,但是拉起会出一个alert弹框 让用户确认,所以无法做到无用户参与的拉起App,如果想要通过外设去执行系统级别的操作,可以让外设接入Siri,可以通过Siri去打电话,接电话,查询天气等,同时在iOS12的系统上可以通过Siri打开应用。自家的App在设置了External accessory communication的background mode之后,能一定程度上保证App不被杀死,但是异常情况不可控,如果是iOS12 以上的系统可以通过Siri打开,但是iOS12以下的系统还是需要手动点击,先把App拉活了之后 再通过语音进行指令控制。但是就算设置了background mode之后,App也只能接收到来自iap的数据,无法完全保证所有的socket连接都不断,在每次接收到来自外设的数据时,还是需要本地做重连机制。
通过iap进行数据交互无论是产品定义还是功能实现都比较简单,因为苹果提供了很完整的文档,能实现啥不能实现啥都清清楚楚的展示出来了,而且iap数据传输带宽高,稳定性强,官方提供的demo直接就能用,需要App开发者要配置的东西很少,大部分的难点都在固件端,所以开发起来除了有后台的问题不好解决,其他都很愉快,但是如果是用ble进行音频传输就会有很多坑,下面一篇文章就是要说的不用iap进行这块的数据传输,改用ble的实现流程。
首先,你要了解你的目的是什么,一般的IOS蓝牙开发有以下三种目的:
1. IOS设备和IOS设备之间交互
好消息是:ios6.0可以把iPhone手机当从设备了,可以两台iPhone通过蓝牙通信传数据了,有点类似spp协议
坏消息是:我们需要的不是这种模式
这种模式通常用于两个使用IOS设备的土豪之间互相切磋游戏,玩个飞车什么的,需要注意的是
2. IOS设备与MFI认证设备交互
什么是MFI认证呢?意思是(Make For ipod/ipad/iphone),只有少数的硬件厂商才有苹果的MFI认证,如果你看到这,你的蓝牙设备还没设计,而且想发布在AppStore上,而且打算使用蓝牙4.0以下(4.0就不需要MFI了,福音),那你还是找个MFI认证的硬件开始做吧,我们公司已经有产品了,不可能重新设计,方案Pass了。
好消息是:如果你的蓝牙模块还没设计,打算在AppStore上发布,打算使用蓝牙4.0以下(IOS设备都兼容),那你就抓紧找MFI认证的蓝牙模块吧
坏消息是:我们不可能重新设计,所以舍弃
3. IOS设备与非IOS设备交互
这就是我们的现状了,根据这个现状,我们能分析出两种情况:
a. 我想做蓝牙4.0以下的,这样iphone4也能用了
好消息:确实可以兼容iphone4,但是由于苹果封闭,没有那个API给你调,苹果上也根本检索不到非IOS设备,所以你就需要越狱了,调用私有Api连接设备
坏消息:只有越狱的手机才能用,发布到AppStore是妄想
b. 我想做蓝牙4.0的,不全兼容也没事,我想发布在AppStore上
好消息:苹果开放了4.0的BLE通道,你可以用BLE通道通信,而且4.0设备也能检索到
坏消息:对IOS版本和IOS设备双重要求,肯定不能全兼容了
所以,根据你自己的实际情况,选择方案,现总结出以下几种方案:
1. 情景:蓝牙2.0,发布在AppStore上
答:使用MFI认证的蓝牙模块设计你的蓝牙产品
特点:不越狱就能用,IOS设备全兼容
2. 情景:蓝牙2.0,不用MFI
答:不能用苹果的Api了,使用私有Api连接设备
特点:只能越狱的手机才能用了,IOS设备全兼容
3. 情景:蓝牙4.0
答:使用BLE通道
特点:IOS设备不完全兼容,无需越狱,无需使用MFI,可发布在AppStore上
注意:一定要看你的蓝牙版本,这样才能选择方案,我们现在设备的版本是2.0,但是Boss想要发布在AppStore上,所以打算升级成4.0的,只有这么办了
1 前言
当前有越来越多的可穿戴设备使用了蓝牙4.0 BLE(Bluetooth Low Energy)。对于iOS开发而言,Apple之前专门推出CoreBluetooth的Framework来支持BLE的开发。对于硬件开发有了解的朋友应该知道,在之前使用低版本的蓝牙的设备,要连接到iOS设备上,需要注册MFI,拥有MFI协议才能进行相应的开发。如果大家关注我之前对LEGO EV3的研究,就可以发现,EV3是使用了蓝牙2.1,因此需要MFI协议来进行开发。
本文将一步一步讲解如何使用CoreBluetooth框架来与各种可穿戴设备进行通信,使用 小米手环 来进行基本的测试。
2 开发环境
1 Macbook Pro Mac OS X 10.10
2 Xcode 6.3.2
3 iPhone 5s v8.1
4 小米手环
3 基本流程
要开发蓝牙,需要对整个通讯过程有个基本了解。这里我摘录一些Apple官方的文档Core Bluetooth Programming Guide的图片来加以说明。这个文档其实对于开发的流程写的是非常的清楚,大家最好可以看一下。
3.1 可穿戴设备与iOS互联方式
从上面这幅图可以看到,我们的iOS设备是Central,用来接收数据和发送命令,而外设比如小米手环是Peripheral,向外传输数据和接收命令。我们要做的就是通过Central来连接Peripheral,然后实现数据的接收和控制指令的发送。在做到这一步之后,再根据具体的硬件,对接收到的数据进行parse解析。
3.2 可穿戴设备蓝牙的数据结构
这里用的是心率设备来做说明,每个外设Peripheral都有对应的服务Service,比如这里是心率Service。一个外设可以有不止一个s、Service。每个service里面可以有多个属性Characteristic,比如这里有两个Characteristic,一个是用来测量心率,一个是用来定位位置。
那么很关键的一点是每个Service,每个Characteristic都是用UUID来确定的。UUID就是每个Service或Characteristic的identifier。
大家可以在iPhone上下载LightBlue这个应用。可以在这里查看一些设备的UUID。
在实际使用中,我们都是要通过UUID来获取数据。这点非常重要。
在CoreBluetooth中,其具体的数据结构图如下:
4 Step-By-Step 上手BLE开发
4.1 Step 1 创建CBCentralManager
从名字上大家可以很清楚的知道,这个类是用来管理BLE的。我们也就是通过这个类来实现连接。
先创建一个:
@property (nonatomic,strong) CBCentralManager *centralManager;
dispatch_queue_t centralQueue = dispatch_queue_create("com.manmanlai", DISPATCH_QUEUE_SERIAL);
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:centralQueue];
然后关键在于CBCentralManagerDelegate的使用。这个之后再讲。
4.2 Step 2 寻找CBPeripheral外设
有了CBCentralManager,接下来就是寻找CBPeripheral外设,方法很简单:
[self.centralManager scanForPeripheralsWithServices:@[] options:nil];
这里的Service就是对应的UUID,如果为空,这scan所有service。
4.3 Step 3 连接CBPeripheral
在上一步中,如果找到了设备,则CBCentralManager的delegate会调用下面的方法:
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
NSLog(@"name:%@",peripheral);
if (!peripheral || !peripheral.name || ([peripheral.name isEqualToString:@""])) {
return;
}
if (!self.peripheral || (self.peripheral.state == CBPeripheralStateDisconnected)) {
self.peripheral = peripheral;
self.peripheral.delegate = self;
NSLog(@"connect peripheral");
[self.centralManager connectPeripheral:peripheral options:nil];
}
}
我们在这里创建了一个CBPeripheral的对象,然后直接连接
CBPeripheral的对象也需要设置delegate.
4.4 Step 4 寻找Service
如果Peripheral连接成功的话,就会调用delegate的方法:
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
if (!peripheral) {
return;
}
[self.centralManager stopScan];
NSLog(@"peripheral did connect");
[self.peripheral discoverServices:nil];
}
我们这里先停止Scan,然后让Peripheral外设寻找其Service。
4.5 Step 5 寻找Characteristic
找到Service后会调用下面的方法:
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
NSArray *services = nil;
if (peripheral != self.peripheral) {
NSLog(@"Wrong Peripheral.\n");
return ;
}
if (error != nil) {
NSLog(@"Error %@\n", error);
return ;
}
services = [peripheral services];
if (!services || ![services count]) {
NSLog(@"No Services");
return ;
}
for (CBService *service in services) {
NSLog(@"service:%@",service.UUID);
[peripheral discoverCharacteristics:nil forService:service];
}
}
我们根据找到的service寻找其对应的Characteristic。
4.6 Step 6 找到Characteristic后读取数据
找到Characteristic后会调用下面的delegate方法:
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
NSLog(@"characteristics:%@",[service characteristics]);
NSArray *characteristics = [service characteristics];
if (peripheral != self.peripheral) {
NSLog(@"Wrong Peripheral.\n");
return ;
}
if (error != nil) {
NSLog(@"Error %@\n", error);
return ;
}
self.characteristic = [characteristics firstObject];
//[self.peripheral readValueForCharacteristic:self.characteristic];
[self.peripheral setNotifyValue:YES forCharacteristic:self.characteristic];
这里我们可以使用readValueForCharacteristic:来读取数据。如果数据是不断更新的,则可以使用setNotifyValue:forCharacteristic:来实现只要有新数据,就获取。
4.7 Step 7 处理数据
读到数据后会调用delegate方法:
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
NSData *data = characteristic.value;
// Parse data ...
}
4.8 Step 8 向设备写数据
这个很简单,只要使用:
[self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
data是NSData类型。