├── .github
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
├── USERGUIDE.md
└── USERGUIDE_CN.md
├── .gitignore
├── .travis.yml
├── Documents
├── .nojekyll
├── README.md
├── _404.md
├── _coverpage.md
├── _media
│ ├── OKBluetooth.sketch
│ ├── banner.png
│ ├── banner@2x.png
│ ├── ble-refact.png
│ ├── icon.png
│ └── icon@2x.png
├── _navbar.md
├── _sidebar.md
├── app.json
├── index.html
└── zh-cn
│ ├── README.md
│ ├── _coverpage.md
│ ├── auto-connect.md
│ ├── auto-safe.md
│ ├── connect.md
│ ├── disconnect.md
│ ├── init.md
│ ├── multi-share.md
│ ├── read.md
│ └── write.md
├── Example
├── OKBluetooth.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── OKBluetooth-Example.xcscheme
├── OKBluetooth
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Images.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── OKAppDelegate.h
│ ├── OKAppDelegate.m
│ ├── OKBluetooth-Info.plist
│ ├── OKBluetooth-Prefix.pch
│ ├── OKCharactersViewController.h
│ ├── OKCharactersViewController.m
│ ├── OKListViewController.h
│ ├── OKListViewController.m
│ ├── OKPeripheralCell.h
│ ├── OKPeripheralCell.m
│ ├── OKPeripheralViewController.h
│ ├── OKPeripheralViewController.m
│ ├── OKViewController.h
│ ├── OKViewController.m
│ ├── en.lproj
│ │ └── InfoPlist.strings
│ └── main.m
├── Podfile
├── Podfile.lock
└── Tests
│ ├── Tests-Info.plist
│ ├── Tests-Prefix.pch
│ ├── Tests.m
│ └── en.lproj
│ └── InfoPlist.strings
├── LICENSE
├── OKBluetooth.podspec
├── OKBluetooth
├── Assets
│ └── .gitkeep
└── Classes
│ ├── .gitkeep
│ ├── CBUUID+StringExtraction.h
│ ├── CBUUID+StringExtraction.m
│ ├── OKBluetooth.h
│ ├── OKCentralManager.h
│ ├── OKCentralManager.m
│ ├── OKCharacteristic.h
│ ├── OKCharacteristic.m
│ ├── OKPeripheral.h
│ ├── OKPeripheral.m
│ ├── OKService.h
│ ├── OKService.m
│ ├── OKUtils.h
│ └── OKUtils.m
├── README.md
├── _Pods.xcodeproj
└── gh-pages.sh
/.github/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## [0.1.0](https://github.com/latehorse/OKBluetooth/releases/tag/0.1.0) (2018-07-31)
4 |
5 | ### Added
6 |
7 | * The easiest way to use Bluetooth (BLE) on ios, using ReactiveCocoa.
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latehorse/OKBluetooth/be258d4fd0b3137a6aa960a5df58a7de687d1b53/.github/CODE_OF_CONDUCT.md
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latehorse/OKBluetooth/be258d4fd0b3137a6aa960a5df58a7de687d1b53/.github/CONTRIBUTING.md
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### 请填写运行环境
2 |
3 | - [x] 设备:`iPhone` / `iPad` / `模拟器`
4 | - [x] 系统:`iOS 11.x`
5 | - [x] Xcode 版本:`9.x`
6 | - [x] OKBluetooth iOS 版本:`0.x.x`
7 |
8 | ### 请描述具体问题
9 |
10 | #### 相关截图(断点的堆栈、控制台的 log)
11 |
--------------------------------------------------------------------------------
/.github/USERGUIDE.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latehorse/OKBluetooth/be258d4fd0b3137a6aa960a5df58a7de687d1b53/.github/USERGUIDE.md
--------------------------------------------------------------------------------
/.github/USERGUIDE_CN.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latehorse/OKBluetooth/be258d4fd0b3137a6aa960a5df58a7de687d1b53/.github/USERGUIDE_CN.md
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata/
15 | *.xccheckout
16 | *.xcworkspace
17 | profile
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 |
23 | # Bundler
24 | .bundle
25 |
26 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
27 | # Carthage/Checkouts
28 |
29 | Carthage/Build
30 |
31 | # We recommend against adding the Pods directory to your .gitignore. However
32 | # you should judge for yourself, the pros and cons are mentioned at:
33 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
34 | #
35 | # Note: if you ignore the Pods directory, make sure to uncomment
36 | # `pod install` in .travis.yml
37 | #
38 | Pods/
39 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # references:
2 | # * https://www.objc.io/issues/6-build-tools/travis-ci/
3 | # * https://github.com/supermarin/xcpretty#usage
4 |
5 | osx_image: xcode9
6 | language: objective-c
7 | branches:
8 | only:
9 | - master
10 | cache: cocoapods
11 | podfile: Example/Podfile
12 | before_install:
13 | - gem install cocoapods --pre --no-rdoc --no-ri --no-document --quiet
14 | - pod setup
15 | - pod install --project-directory=Example
16 | script:
17 | - set -o pipefail
18 | - xcodebuild -version
19 | - xcodebuild -showsdks
20 | - xcodebuild -workspace Example/OKBluetooth.xcworkspace -scheme OKBluetooth-Example -sdk iphonesimulator11.0 ONLY_ACTIVE_ARCH=NO | xcpretty
21 | - pod lib lint --allow-warnings
22 |
23 | after_success:
24 | - bash <(curl -s https://codecov.io/bash)
25 | - sleep 3
--------------------------------------------------------------------------------
/Documents/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latehorse/OKBluetooth/be258d4fd0b3137a6aa960a5df58a7de687d1b53/Documents/.nojekyll
--------------------------------------------------------------------------------
/Documents/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://travis-ci.org/latehorse/OKBluetooth)
4 | [](https://cocoapods.org/pods/OKBluetooth)
5 | [](https://cocoapods.org/pods/OKBluetooth)
6 | [](https://cocoapods.org/pods/OKBluetooth)
7 | [](https://cocoapods.org/pods/OKBluetooth)
8 | [](https://cocoapods.org/pods/OKBluetooth)
9 | [](https://cocoapods.org/pods/OKBluetooth)
10 |
11 | ## Example
12 |
13 | To run the example project, clone the repo, and run `pod install` from the Example directory first.
14 |
15 | ## Requirements
16 |
17 | * XCode 8.0+
18 | * iOS 8.0+
19 |
20 | ## Installation
21 |
22 | OKBluetooth is available through [CocoaPods](https://cocoapods.org). To install
23 | it, simply add the following line to your Podfile:
24 |
25 | ```ruby
26 | pod 'OKBluetooth'
27 | ```
28 |
29 | ## Author
30 |
31 | yuhanle, deadvia@gmail.com
32 |
33 | ## License
34 |
35 | OKBluetooth is available under the MIT license. See the LICENSE file for more info.
--------------------------------------------------------------------------------
/Documents/_404.md:
--------------------------------------------------------------------------------
1 | 你来到了知识的荒原~
2 |
3 | 还是去[这里](https://github.com/latehorse/OKBluetooth)看一下吧!没准儿能找到你想要的
--------------------------------------------------------------------------------
/Documents/_coverpage.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # [OKBluetooth](#okbluetooth)
4 |
5 | [Github](https://github.com/latehorse/OKBluetooth) [Get Started](#okbluetooth)
--------------------------------------------------------------------------------
/Documents/_media/OKBluetooth.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latehorse/OKBluetooth/be258d4fd0b3137a6aa960a5df58a7de687d1b53/Documents/_media/OKBluetooth.sketch
--------------------------------------------------------------------------------
/Documents/_media/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latehorse/OKBluetooth/be258d4fd0b3137a6aa960a5df58a7de687d1b53/Documents/_media/banner.png
--------------------------------------------------------------------------------
/Documents/_media/banner@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latehorse/OKBluetooth/be258d4fd0b3137a6aa960a5df58a7de687d1b53/Documents/_media/banner@2x.png
--------------------------------------------------------------------------------
/Documents/_media/ble-refact.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latehorse/OKBluetooth/be258d4fd0b3137a6aa960a5df58a7de687d1b53/Documents/_media/ble-refact.png
--------------------------------------------------------------------------------
/Documents/_media/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latehorse/OKBluetooth/be258d4fd0b3137a6aa960a5df58a7de687d1b53/Documents/_media/icon.png
--------------------------------------------------------------------------------
/Documents/_media/icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latehorse/OKBluetooth/be258d4fd0b3137a6aa960a5df58a7de687d1b53/Documents/_media/icon@2x.png
--------------------------------------------------------------------------------
/Documents/_navbar.md:
--------------------------------------------------------------------------------
1 | * [En](/)
2 | * [中文](/zh-cn/)
--------------------------------------------------------------------------------
/Documents/_sidebar.md:
--------------------------------------------------------------------------------
1 |
2 | * 入门
3 | * [首页](zh-cn/)
4 | * [初始化](zh-cn/init)
5 |
6 | * 连接
7 | * [连接](zh-cn/connect)
8 | * [断开](zh-cn/disconnect)
9 |
10 | * 读写
11 | * [读数据](zh-cn/read)
12 | * [写数据](zh-cn/write)
13 |
14 | * 拓展阅读
15 | * [多设备共享](zh-cn/multi-share)
16 | * [感应设撤防](zh-cn/auto-safe)
17 | * [后台自动重连](zh-cn/auto-connect)
--------------------------------------------------------------------------------
/Documents/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "apps": [{
3 | "name": "okbluetooth-v1",
4 | "script": "http-server",
5 | "args": "-p 9100"
6 | }]
7 | }
--------------------------------------------------------------------------------
/Documents/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | OKBluetooth
6 |
7 |
8 |
9 |
10 |
11 |
12 | Loading
13 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/Documents/zh-cn/README.md:
--------------------------------------------------------------------------------
1 | # OKBluetooth
2 |
3 | !> 在使用本项目前,需熟练使用 `ReactiveObjC` 相关模块
4 |
5 | SDK 主要提供功能如下:
6 |
7 | - 初始化
8 | - 连接断开
9 | - 读取数据
10 | - 写入数据
11 | - 后台自动重连
12 | - OTA (拓展功能)
13 |
14 | 以上均是 SDK 的基础功能,对于不同的模块,都有公开与私有 `API`,详情参考示例
15 |
16 | 
17 |
--------------------------------------------------------------------------------
/Documents/zh-cn/_coverpage.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # [OKBlutooth](#okbluetooth)
4 |
5 | [Github](https://github.com/latehorse/OKBluetooth) [马上开始](#okbluetooth)
--------------------------------------------------------------------------------
/Documents/zh-cn/auto-connect.md:
--------------------------------------------------------------------------------
1 | 断开自动重连的实现,即在收到断开连接消息时,调用提供的`connect` 方法,手机端会持续连接该设备直至连接成功为止。
2 |
3 | 自动重连的方法类似,是在收到异常断开连接的回调中,调用底层 `connect` 的方法,在下次靠近设备时,系统会帮助APP 自动重连。
4 |
5 | 在以上自动重连的过程中,会存在几个问题:
6 |
7 | * 下次靠近设备的时间不确定,可能在靠近的时间,设备已经被其他方式解绑,此时重连设备是不合理的;
8 | * 其他类似上面的场景,都会存在重连设备不合理的问题。
9 |
10 | ### 安卓问题:
11 |
12 | * 后台重连时间过长(10s左右)
13 |
14 | ### iOS 问题:
15 |
16 | * 在用户手动kill 掉应用,概率性无法连接成功,需要打开APP 后重新连接
17 | * 重连时间过长(10s左右)
18 |
19 | ### 利用连接 Peripheral 时的选项
20 |
21 | Foreground-Only app 在挂起的时候,便会加入到系统的一个队列中,当程序重新唤醒时,系统便会通知程序。Core Bluetooth 会在程序中包含 central 时,给用户以提示。用户可根据提示来判断是否要唤醒该 app。
22 |
23 | 你可以利用 central 在连接 peripheral 时的方法 `connectPeripheral:options:` 中的 `options `来触发提示:
24 |
25 | * `CBConnectPeripheralOptionNotifyOnConnectionKey` —— 在连接成功后,程序被挂起,给出系统提示。
26 | * `CBConnectPeripheralOptionNotifyOnDisconnectionKey` —— 在程序挂起,蓝牙连接断开时,给出系统提示。
27 | * `CBConnectPeripheralOptionNotifyOnNotificationKey` —— 在程序挂起后,收到 peripheral 数据时,给出系统提示。
28 |
29 | ### Core Bluetooth 后台模式
30 |
31 | 如果你想让你的 app 能在后台运行蓝牙,那么必须在 info.plist 中打开蓝牙的后台运行模式。当配置之后,收到相关事件便会从后台唤醒。这一机制对定期接收数据的 app 很有用,比如心率监测器。
32 |
33 | ### 作为 Central 的后台模式
34 |
35 | 如果在 info.plist 中配置了 UIBackgroundModes – bluetooth-central,那么系统则允许程序在后台处理蓝牙相关事件。在程序进入后台后,依然能扫描、搜索 peripheral,并且还能进行数据交互。当 CBCentralManagerDelegate 和 CBPeripheralDelegate 的代理方法被调用时,系统将会唤醒程序。此时允许你去处理重要的事件,比如:连接的建立或断开,peripheral 发送了数据,central manager 的状态改变。
36 |
37 | 虽然此时程序能在后台运行,但是对 peripheral 的扫描和在前台时是不一样的。实际情况是这样的:
38 |
39 | * 设置的 `CBCentralManagerScanOptionAllowDuplicatesKey` 将失效,并将发现的多个 peripheral 广播的事件合并为一个。
40 | * 如果全部的 app 都在后台搜索 peripheral,那么每次搜索的时间间隔会更大。这会导致搜索到 peripheral 的时间变长。
41 |
42 | 这些相应的调整会减少无线电使用,并提升续航能力。
43 |
44 | ### 巧妙的使用后台模式
45 |
46 | 虽然程序支持一个或多个 Core Bluetooth 服务在后台运行,但也不要滥用。因为蓝牙服务会占用 iOS 设备的无线电资源,这也会间接影响到续航能力,所以尽可能少的去使用后台模式。app 会唤醒程序并处理相关事务,完成后又会快速回到挂起状态。
47 |
48 | 无论是 central 还是 peripheral,要支持后台模式都应该遵循以下几点:
49 |
50 | * 程序应该提供 UI,让用户决定是否要在后台运行。
51 | * 一旦程序在后台被唤醒,程序只有 10s 的时间来处理相关事务。所以应该在程序再次挂起前处理完事件。后台运行的太耗时的程序会被系统强制关闭进程。
52 | * 处理无关的事件不应该唤醒程序。
53 |
54 | 和后台运行的更多介绍,可以查看 [App Programming Guide for iOS](https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40007072)。
--------------------------------------------------------------------------------
/Documents/zh-cn/auto-safe.md:
--------------------------------------------------------------------------------
1 | > 手机APP对BLE蓝牙的操作
2 | > 当手机靠近后,2米会发出撤防指令,在非一键启动状态下,远离5米后发出设防指令
3 |
4 | ### 计算感应距离
5 |
6 | 目前,通过读取蓝牙的广播信息,只能读取到rssi(信号强度),据说蓝牙外设可以计算出和central(中央)的距离,那么就有两种方式计算感应距离:
7 |
8 | * 手机APP 根据rssi 计算
9 |
10 | 算法很多,可以找到尽量准确的算法来计算
11 |
12 | * 蓝牙外设在感知距离变化时主动发送订阅通知
13 |
14 | 看蓝牙外设的具体情况,如果支持计算距离或者rssi,均可优化
15 |
16 | ### 设撤防逻辑
17 |
18 | #### 手机APP 操控
19 |
20 | 感应设撤防,应当在不影响用户操作的前提下,进一步保证安全
21 |
22 | 比如,用户在撤防以后,离开车辆,在达到危险距离时,APP 会发送一个设防指令,此时已然是连接状态;
23 |
24 | #### 蓝牙外设操控
25 |
26 | 这种方案比较简单,而且安全等级更高
27 |
28 | 首先,可以通过APP 设置,配置蓝牙外设的感应功能,如果设置了感应设撤防,那在蓝牙与手机连接时,自动撤防;断开时,自动设防。
29 |
30 | 当然也可以通过APP,直接把感应设防的设置同步到蓝牙外设,以后外设在出发设撤防的情况下,自动操作,无需APP 发指令。
31 |
32 | 在断开以后,手机APP 由于断开的原因,是不知道外设的状态,仅能通过GPS 上报中控状态,最后在同步至APP。
--------------------------------------------------------------------------------
/Documents/zh-cn/connect.md:
--------------------------------------------------------------------------------
1 | ## 连接设备
2 |
3 | 在扫描到需要连接的设备后,通过 `connectCommand` 命令,开始连接服务。`input` 为连接的超时时间
4 |
5 | 示例代码
6 |
7 | ``` objectivec
8 | @weakify(self);
9 | [[_clickBtn rac_signalForControlEvents:UIControlEventTouchUpInside]
10 | subscribeNext:^(__kindof UIControl * _Nullable x) {
11 | @strongify(self);
12 | if (self.peripheral.isConnected) {
13 | [[self.peripheral.disConnectCommand execute:@1] subscribeNext:^(id _Nullable x) {
14 |
15 | } error:^(NSError * _Nullable error) {
16 | NSLog(@"%@", error);
17 | } completed:^{
18 | dispatch_async(dispatch_get_main_queue(), ^{
19 | self.peripheral = _peripheral;
20 | });
21 | }];
22 | }else {
23 | [SVProgressHUD showWithStatus:@"连接中"];
24 | [[self.peripheral.connectCommand execute:@30] subscribeNext:^(id _Nullable x) {
25 |
26 | } error:^(NSError * _Nullable error) {
27 | NSLog(@"%@", error);
28 | dispatch_async(dispatch_get_main_queue(), ^{
29 | [SVProgressHUD dismiss];
30 | });
31 | } completed:^{
32 | dispatch_async(dispatch_get_main_queue(), ^{
33 | self.peripheral = _peripheral;
34 | [SVProgressHUD dismiss];
35 | });
36 | }];
37 | }
38 | }];
39 | ```
40 |
41 | !> 另外,`OKPeripheral` 提供一个 `RSSI` 订阅服务,信号强度的变化,会通过该信号发布。
42 |
43 | ## 扫描服务
44 |
45 | 连接成功后,可以开始搜索设备的服务信息,执行 `discoverServicesCommand` 命令,`input` 表示过滤的 `serviceUUIDs` 数组,默认为空。
46 |
47 | !> 最终搜索到的服务,会存放在 `OKPeripheral` 属性下
48 |
49 | 示例代码
50 |
51 | ``` objectivec
52 | [[self.okph.discoverServicesCommand execute:@[]] subscribeNext:^(OKPeripheral *x) {
53 | [self.okItems removeAllObjects];
54 | [self.okItems addObjectsFromArray:x.services];
55 | } error:^(NSError * _Nullable error) {
56 | NSLog(@"%@", error);
57 | } completed:^{
58 | dispatch_async(dispatch_get_main_queue(), ^{
59 | [self.tableView reloadData];
60 | });
61 | }];
62 | ```
63 |
64 | ## 搜索特征
65 |
66 | 在搜索到的服务中,执行 `discoverCharacteristicsCommand` 命令,即可搜索指定服务的特征值,便于后续订阅通知,读写数据操作
67 |
68 | 代码示例
69 |
70 | ```
71 | [[self.service.discoverCharacteristicsCommand execute:@[]] subscribeNext:^(OKService *x) {
72 | [self.okItems removeAllObjects];
73 | [self.okItems addObjectsFromArray:x.characteristics];
74 | } error:^(NSError * _Nullable error) {
75 | NSLog(@"%@", error);
76 | } completed:^{
77 | dispatch_async(dispatch_get_main_queue(), ^{
78 | [self.tableView reloadData];
79 | });
80 | }];
81 | ```
82 |
83 | 至此,连接的基本功能就介绍完了
84 |
--------------------------------------------------------------------------------
/Documents/zh-cn/disconnect.md:
--------------------------------------------------------------------------------
1 | ## 断开设备
2 |
3 | 需要断开连接时,通过 `disConnectCommand` 命令,执行断开。`input` 没有意义
4 |
5 | 示例
6 |
7 | ```objectivec
8 | @weakify(self);
9 | [[_clickBtn rac_signalForControlEvents:UIControlEventTouchUpInside]
10 | subscribeNext:^(__kindof UIControl * _Nullable x) {
11 | @strongify(self);
12 | if (self.peripheral.isConnected) {
13 | [[self.peripheral.disConnectCommand execute:@1] subscribeNext:^(id _Nullable x) {
14 |
15 | } error:^(NSError * _Nullable error) {
16 | NSLog(@"%@", error);
17 | } completed:^{
18 | dispatch_async(dispatch_get_main_queue(), ^{
19 | self.peripheral = _peripheral;
20 | });
21 | }];
22 | }else {
23 | [SVProgressHUD showWithStatus:@"连接中"];
24 | [[self.peripheral.connectCommand execute:@30] subscribeNext:^(id _Nullable x) {
25 |
26 | } error:^(NSError * _Nullable error) {
27 | NSLog(@"%@", error);
28 | dispatch_async(dispatch_get_main_queue(), ^{
29 | [SVProgressHUD dismiss];
30 | });
31 | } completed:^{
32 | dispatch_async(dispatch_get_main_queue(), ^{
33 | self.peripheral = _peripheral;
34 | [SVProgressHUD dismiss];
35 | });
36 | }];
37 | }
38 | }];
39 | ```
40 |
41 | !> 如果是异常断开,你需要到连接信号的订阅中,手动调用一次 `connect` 才可以顺利实现自动重连的功能
--------------------------------------------------------------------------------
/Documents/zh-cn/init.md:
--------------------------------------------------------------------------------
1 | !> 为了保证手机端作为 `central`,同时保持多个外设连接和数据接发,蓝牙模块采用单例模式,提供基础功能并暴露部分接口便于接入方调用
2 |
3 | ## 初始化
4 |
5 | 仅需初始化 `OKCentralManager`,就会完成整个模块初始化。
6 |
7 | ``` objectivec
8 | [OKCentralManager sharedInstance];
9 | //设置最大搜索设备数量 默认一直搜索至超时或手动停止
10 | [OKCentralManager sharedInstance].peripheralsCountToStop = 20;
11 | ```
12 |
13 | ## 信号量
14 |
15 | 提供了几个常用的信号量,如蓝牙状态变化,连接以及断开,可以随时订阅取消。
16 |
17 | 示例代码
18 |
19 | ``` objectivec
20 | [[OKCentralManager sharedInstance].scanForPeripheralsCommand.executing subscribeNext:^(NSNumber * _Nullable x) {
21 | if (x.boolValue) {
22 | [SVProgressHUD showWithStatus:@"搜索中"];
23 | } else {
24 | [SVProgressHUD dismiss];
25 | }
26 | }];
27 |
28 | @weakify(self);
29 | [[OKCentralManager sharedInstance].centralManagerStateConnection.signal subscribeNext:^(NSNumber *x) {
30 | @strongify(self);
31 | if (x.integerValue == CBManagerStatePoweredOn && ![OKCentralManager sharedInstance].isScanning) {
32 | [self _action_refresh:nil];
33 | }
34 | }];
35 |
36 | [[OKCentralManager sharedInstance].connectPeripheralConnection.signal subscribeNext:^(id _Nullable x) {
37 | NSLog(@"Connection info: %@", x);
38 | }];
39 | ```
40 |
41 | ## 扫描外设
42 |
43 | `service uuid: 0x0000FEE9-0000-1000-8000-00805F9B34FB`
44 |
45 | 初始化`OKCentralManager`成功后,可以通过执行 `scanForPeripheralsCommand` 开始扫描外设
46 |
47 | 参数说明
48 |
49 | | 功能 | 扫描外设 |
50 | | ------------ | ------------ |
51 | | input | OKScanModel |
52 |
53 | 初始化扫描参数,具体参数内容可参考注释,默认超时时间 `30` 秒。
54 |
55 | ``` objectivec
56 | OKScanModel *input = [[OKScanModel alloc] initModelWithServiceUUIDs:nil options:nil aScanInterval:30];
57 | ```
58 |
59 | 执行扫描指令后,指令返回的结果可从 `next` 中订阅,`error` 中可以处理扫描过程中遇到的错误
60 |
61 | !> 为了实时更新扫描列表,在 `next` 信号中默认加了 `1.5 s` 的 `throttle`,否则会出现疯狂的信号值变化
62 |
63 | 示例代码
64 |
65 | ``` objectivec
66 | @weakify(self);
67 | self.dsp = [[[OKCentralManager sharedInstance].scanForPeripheralsCommand execute:input] subscribeNext:^(NSArray *peripherals) {
68 | @strongify(self);
69 | [self.okItems removeAllObjects];
70 | for (OKPeripheral *p in peripherals) {
71 | if (p.name.length) {
72 | [self.okItems addObject:p];
73 | }
74 | }
75 | dispatch_async(dispatch_get_main_queue(), ^{
76 | [self.tableView reloadData];
77 | });
78 | } error:^(NSError * _Nullable error) {
79 | NSLog(@"%@", error);
80 | dispatch_async(dispatch_get_main_queue(), ^{
81 | if (error.code == 4) {
82 | [SVProgressHUD showErrorWithStatus:@"蓝牙未开启"];
83 | }else if (error.code == kOKUtilsScanTimeoutErrorCode) {
84 | [SVProgressHUD showErrorWithStatus:@"扫描超时"];
85 | }
86 | });
87 | }];
88 | ```
89 |
--------------------------------------------------------------------------------
/Documents/zh-cn/multi-share.md:
--------------------------------------------------------------------------------
1 | 手机端作为 `central` 可以同时连接多个外设,通常情况下仅会连接一个外设使用,对于多设备连接,这里限制最多 `5-7`个,底层模块需要维持和设备的连接状态,以及正常通信。
2 |
3 | ## 如何维持多设备连接
4 |
5 | 默认情况下,我们支持多设备连接和通信,由`OKBluetooth` 负责维护连接中的所有设备以及通信。
6 |
7 | 
8 |
9 | 如上如所示,`OKBluetooth` 同时与多个设备保持连接,业务方可以在初始化后,告知`OKBluetooth` 自己想要联系的部分设备,从扫描到连接再到接发数据,均由`OKBluetooth`来完成,最后又`OKBluetooth` 返回结果。
10 |
11 | ## 多设备通信
12 |
13 | 
14 |
--------------------------------------------------------------------------------
/Documents/zh-cn/read.md:
--------------------------------------------------------------------------------
1 | 读数据为了避免恶意调用读取,使用 `RAC` 后,执行 `command` 时,只有等上一个命令完成,才可以再次执行该命令,保证了消息顺序。
2 |
3 | ## 订阅通知
4 |
5 | 订阅通知这里,同时提供了一个 `notifyValueSignal`,便于其他业务直接通过订阅信号的方式,获取到数据。只需在搜索到服务以后,打开订阅模式即可。
6 |
7 | 代码示例
8 |
9 | ``` objectivec
10 | [[okph.notifyValueCommand execute:@YES] subscribeNext:^(id _Nullable x) {
11 | NSLog(@"notify: %@", x);
12 | } error:^(NSError * _Nullable error) {
13 | NSLog(@"notifyError: %@", error);
14 | }];
15 | ```
16 |
17 | ## 读取数据
18 |
19 | 读取指定特征值的数据,执行 `readValueCommand`,订阅读取结果
20 |
21 | 代码示例
22 |
23 | ``` objectivec
24 | [[okph.readValueCommand execute:@0] subscribeNext:^(id _Nullable x) {
25 | NSLog(@"readValue: %@", x);
26 | }];
27 | ```
28 |
--------------------------------------------------------------------------------
/Documents/zh-cn/write.md:
--------------------------------------------------------------------------------
1 | 本项目提供了长数据的写操作,在内部实现了一个 `loop` 操作,分包写入,目前固定包的大小是 `20` 字节
2 |
3 | ## 写入数据
4 |
5 | 先定义一个写入参数,并把数据赋值,在执行 `writeValueCommand` 命令
6 |
7 | ``` objectivec
8 | OKWriteValueModel *model = [[OKWriteValueModel alloc] init];
9 | model.type = CBCharacteristicWriteWithoutResponse;
10 |
11 | Byte byte[] = {1, 3};
12 | model.data = [NSData dataWithBytes:byte length:2];
13 |
14 | [[okph.writeValueCommand execute:model] subscribeNext:^(id _Nullable x) {
15 | NSLog(@"writeValue: %@", x);
16 | } error:^(NSError * _Nullable error) {
17 | NSLog(@"writeValue: %@", error);
18 | } completed:^{
19 | NSLog(@"writeSuccess");
20 | }];
21 | ```
22 |
23 | !> 如果是较大的数据包,可以从 `next` 中订阅到当前发送的进度,返回是 `OKWriteValueModel` 的实例,可以根据 `offset` 计算。
24 |
--------------------------------------------------------------------------------
/Example/OKBluetooth.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 20E9340584B84C96E3D2FC98 /* libPods-OKBluetooth_Tests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B38C879A4B9BAE622D140B75 /* libPods-OKBluetooth_Tests.a */; };
11 | 2BF75013C719C547218AA978 /* libPods-OKBluetooth_Example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C65B3E83A2900DDB2BE044C /* libPods-OKBluetooth_Example.a */; };
12 | 416AA25D210EB5B7005AC605 /* OKPeripheralCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 416AA25C210EB5B7005AC605 /* OKPeripheralCell.m */; };
13 | 416AA260210EBB5C005AC605 /* OKPeripheralViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 416AA25F210EBB5C005AC605 /* OKPeripheralViewController.m */; };
14 | 416AA263210EFC50005AC605 /* OKCharactersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 416AA262210EFC50005AC605 /* OKCharactersViewController.m */; };
15 | 41F85F13210B2220002DE259 /* OKListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 41F85F12210B2220002DE259 /* OKListViewController.m */; };
16 | 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
17 | 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; };
18 | 6003F592195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
19 | 6003F598195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F596195388D20070C39A /* InfoPlist.strings */; };
20 | 6003F59A195388D20070C39A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F599195388D20070C39A /* main.m */; };
21 | 6003F59E195388D20070C39A /* OKAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F59D195388D20070C39A /* OKAppDelegate.m */; };
22 | 6003F5A7195388D20070C39A /* OKViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5A6195388D20070C39A /* OKViewController.m */; };
23 | 6003F5A9195388D20070C39A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5A8195388D20070C39A /* Images.xcassets */; };
24 | 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; };
25 | 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
26 | 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
27 | 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; };
28 | 6003F5BC195388D20070C39A /* Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5BB195388D20070C39A /* Tests.m */; };
29 | 71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */; };
30 | 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; };
31 | /* End PBXBuildFile section */
32 |
33 | /* Begin PBXContainerItemProxy section */
34 | 6003F5B3195388D20070C39A /* PBXContainerItemProxy */ = {
35 | isa = PBXContainerItemProxy;
36 | containerPortal = 6003F582195388D10070C39A /* Project object */;
37 | proxyType = 1;
38 | remoteGlobalIDString = 6003F589195388D20070C39A;
39 | remoteInfo = OKBluetooth;
40 | };
41 | /* End PBXContainerItemProxy section */
42 |
43 | /* Begin PBXFileReference section */
44 | 1B26FEF3BC1D8695FA5E6EC3 /* Pods-OKBluetooth_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OKBluetooth_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-OKBluetooth_Tests/Pods-OKBluetooth_Tests.debug.xcconfig"; sourceTree = ""; };
45 | 1C65B3E83A2900DDB2BE044C /* libPods-OKBluetooth_Example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OKBluetooth_Example.a"; sourceTree = BUILT_PRODUCTS_DIR; };
46 | 273A1D9CE48D6391BB4834CC /* Pods-OKBluetooth_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OKBluetooth_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-OKBluetooth_Example/Pods-OKBluetooth_Example.release.xcconfig"; sourceTree = ""; };
47 | 416AA25B210EB5B7005AC605 /* OKPeripheralCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OKPeripheralCell.h; sourceTree = ""; };
48 | 416AA25C210EB5B7005AC605 /* OKPeripheralCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OKPeripheralCell.m; sourceTree = ""; };
49 | 416AA25E210EBB5C005AC605 /* OKPeripheralViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OKPeripheralViewController.h; sourceTree = ""; };
50 | 416AA25F210EBB5C005AC605 /* OKPeripheralViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OKPeripheralViewController.m; sourceTree = ""; };
51 | 416AA261210EFC50005AC605 /* OKCharactersViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OKCharactersViewController.h; sourceTree = ""; };
52 | 416AA262210EFC50005AC605 /* OKCharactersViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OKCharactersViewController.m; sourceTree = ""; };
53 | 41F85F11210B2220002DE259 /* OKListViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OKListViewController.h; sourceTree = ""; };
54 | 41F85F12210B2220002DE259 /* OKListViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OKListViewController.m; sourceTree = ""; };
55 | 6003F58A195388D20070C39A /* OKBluetooth_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OKBluetooth_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
56 | 6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
57 | 6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
58 | 6003F591195388D20070C39A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
59 | 6003F595195388D20070C39A /* OKBluetooth-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "OKBluetooth-Info.plist"; sourceTree = ""; };
60 | 6003F597195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; };
61 | 6003F599195388D20070C39A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
62 | 6003F59B195388D20070C39A /* OKBluetooth-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OKBluetooth-Prefix.pch"; sourceTree = ""; };
63 | 6003F59C195388D20070C39A /* OKAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OKAppDelegate.h; sourceTree = ""; };
64 | 6003F59D195388D20070C39A /* OKAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OKAppDelegate.m; sourceTree = ""; };
65 | 6003F5A5195388D20070C39A /* OKViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OKViewController.h; sourceTree = ""; };
66 | 6003F5A6195388D20070C39A /* OKViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OKViewController.m; sourceTree = ""; };
67 | 6003F5A8195388D20070C39A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
68 | 6003F5AE195388D20070C39A /* OKBluetooth_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OKBluetooth_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
69 | 6003F5AF195388D20070C39A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
70 | 6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = ""; };
71 | 6003F5B9195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; };
72 | 6003F5BB195388D20070C39A /* Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Tests.m; sourceTree = ""; };
73 | 606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = ""; };
74 | 71719F9E1E33DC2100824A3D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
75 | 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = ""; };
76 | 9DC3C0C3BB1D0B42BFBBB96B /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; };
77 | A1585B52F74D3648C408B0FD /* OKBluetooth.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = OKBluetooth.podspec; path = ../OKBluetooth.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
78 | B38C879A4B9BAE622D140B75 /* libPods-OKBluetooth_Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OKBluetooth_Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
79 | B99FCCBC6489B0D7E582E02A /* Pods-OKBluetooth_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OKBluetooth_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-OKBluetooth_Example/Pods-OKBluetooth_Example.debug.xcconfig"; sourceTree = ""; };
80 | D85183C2BF4757FC50FF294C /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; };
81 | EDD65610381486BEEAA53A58 /* Pods-OKBluetooth_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OKBluetooth_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-OKBluetooth_Tests/Pods-OKBluetooth_Tests.release.xcconfig"; sourceTree = ""; };
82 | /* End PBXFileReference section */
83 |
84 | /* Begin PBXFrameworksBuildPhase section */
85 | 6003F587195388D20070C39A /* Frameworks */ = {
86 | isa = PBXFrameworksBuildPhase;
87 | buildActionMask = 2147483647;
88 | files = (
89 | 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */,
90 | 6003F592195388D20070C39A /* UIKit.framework in Frameworks */,
91 | 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */,
92 | 2BF75013C719C547218AA978 /* libPods-OKBluetooth_Example.a in Frameworks */,
93 | );
94 | runOnlyForDeploymentPostprocessing = 0;
95 | };
96 | 6003F5AB195388D20070C39A /* Frameworks */ = {
97 | isa = PBXFrameworksBuildPhase;
98 | buildActionMask = 2147483647;
99 | files = (
100 | 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */,
101 | 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */,
102 | 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */,
103 | 20E9340584B84C96E3D2FC98 /* libPods-OKBluetooth_Tests.a in Frameworks */,
104 | );
105 | runOnlyForDeploymentPostprocessing = 0;
106 | };
107 | /* End PBXFrameworksBuildPhase section */
108 |
109 | /* Begin PBXGroup section */
110 | 6003F581195388D10070C39A = {
111 | isa = PBXGroup;
112 | children = (
113 | 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */,
114 | 6003F593195388D20070C39A /* Example for OKBluetooth */,
115 | 6003F5B5195388D20070C39A /* Tests */,
116 | 6003F58C195388D20070C39A /* Frameworks */,
117 | 6003F58B195388D20070C39A /* Products */,
118 | 98062539EA2E7EED80AAD200 /* Pods */,
119 | );
120 | sourceTree = "";
121 | };
122 | 6003F58B195388D20070C39A /* Products */ = {
123 | isa = PBXGroup;
124 | children = (
125 | 6003F58A195388D20070C39A /* OKBluetooth_Example.app */,
126 | 6003F5AE195388D20070C39A /* OKBluetooth_Tests.xctest */,
127 | );
128 | name = Products;
129 | sourceTree = "";
130 | };
131 | 6003F58C195388D20070C39A /* Frameworks */ = {
132 | isa = PBXGroup;
133 | children = (
134 | 6003F58D195388D20070C39A /* Foundation.framework */,
135 | 6003F58F195388D20070C39A /* CoreGraphics.framework */,
136 | 6003F591195388D20070C39A /* UIKit.framework */,
137 | 6003F5AF195388D20070C39A /* XCTest.framework */,
138 | 1C65B3E83A2900DDB2BE044C /* libPods-OKBluetooth_Example.a */,
139 | B38C879A4B9BAE622D140B75 /* libPods-OKBluetooth_Tests.a */,
140 | );
141 | name = Frameworks;
142 | sourceTree = "";
143 | };
144 | 6003F593195388D20070C39A /* Example for OKBluetooth */ = {
145 | isa = PBXGroup;
146 | children = (
147 | 6003F59C195388D20070C39A /* OKAppDelegate.h */,
148 | 6003F59D195388D20070C39A /* OKAppDelegate.m */,
149 | 6003F5A5195388D20070C39A /* OKViewController.h */,
150 | 6003F5A6195388D20070C39A /* OKViewController.m */,
151 | 41F85F11210B2220002DE259 /* OKListViewController.h */,
152 | 41F85F12210B2220002DE259 /* OKListViewController.m */,
153 | 416AA25E210EBB5C005AC605 /* OKPeripheralViewController.h */,
154 | 416AA25F210EBB5C005AC605 /* OKPeripheralViewController.m */,
155 | 416AA261210EFC50005AC605 /* OKCharactersViewController.h */,
156 | 416AA262210EFC50005AC605 /* OKCharactersViewController.m */,
157 | 416AA25B210EB5B7005AC605 /* OKPeripheralCell.h */,
158 | 416AA25C210EB5B7005AC605 /* OKPeripheralCell.m */,
159 | 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */,
160 | 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */,
161 | 6003F5A8195388D20070C39A /* Images.xcassets */,
162 | 6003F594195388D20070C39A /* Supporting Files */,
163 | );
164 | name = "Example for OKBluetooth";
165 | path = OKBluetooth;
166 | sourceTree = "";
167 | };
168 | 6003F594195388D20070C39A /* Supporting Files */ = {
169 | isa = PBXGroup;
170 | children = (
171 | 6003F595195388D20070C39A /* OKBluetooth-Info.plist */,
172 | 6003F596195388D20070C39A /* InfoPlist.strings */,
173 | 6003F599195388D20070C39A /* main.m */,
174 | 6003F59B195388D20070C39A /* OKBluetooth-Prefix.pch */,
175 | );
176 | name = "Supporting Files";
177 | sourceTree = "";
178 | };
179 | 6003F5B5195388D20070C39A /* Tests */ = {
180 | isa = PBXGroup;
181 | children = (
182 | 6003F5BB195388D20070C39A /* Tests.m */,
183 | 6003F5B6195388D20070C39A /* Supporting Files */,
184 | );
185 | path = Tests;
186 | sourceTree = "";
187 | };
188 | 6003F5B6195388D20070C39A /* Supporting Files */ = {
189 | isa = PBXGroup;
190 | children = (
191 | 6003F5B7195388D20070C39A /* Tests-Info.plist */,
192 | 6003F5B8195388D20070C39A /* InfoPlist.strings */,
193 | 606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */,
194 | );
195 | name = "Supporting Files";
196 | sourceTree = "";
197 | };
198 | 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */ = {
199 | isa = PBXGroup;
200 | children = (
201 | A1585B52F74D3648C408B0FD /* OKBluetooth.podspec */,
202 | 9DC3C0C3BB1D0B42BFBBB96B /* README.md */,
203 | D85183C2BF4757FC50FF294C /* LICENSE */,
204 | );
205 | name = "Podspec Metadata";
206 | sourceTree = "";
207 | };
208 | 98062539EA2E7EED80AAD200 /* Pods */ = {
209 | isa = PBXGroup;
210 | children = (
211 | B99FCCBC6489B0D7E582E02A /* Pods-OKBluetooth_Example.debug.xcconfig */,
212 | 273A1D9CE48D6391BB4834CC /* Pods-OKBluetooth_Example.release.xcconfig */,
213 | 1B26FEF3BC1D8695FA5E6EC3 /* Pods-OKBluetooth_Tests.debug.xcconfig */,
214 | EDD65610381486BEEAA53A58 /* Pods-OKBluetooth_Tests.release.xcconfig */,
215 | );
216 | name = Pods;
217 | sourceTree = "";
218 | };
219 | /* End PBXGroup section */
220 |
221 | /* Begin PBXNativeTarget section */
222 | 6003F589195388D20070C39A /* OKBluetooth_Example */ = {
223 | isa = PBXNativeTarget;
224 | buildConfigurationList = 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "OKBluetooth_Example" */;
225 | buildPhases = (
226 | 04803207D869908C02DB028F /* [CP] Check Pods Manifest.lock */,
227 | 6003F586195388D20070C39A /* Sources */,
228 | 6003F587195388D20070C39A /* Frameworks */,
229 | 6003F588195388D20070C39A /* Resources */,
230 | 010FDEE87BFE3EE4BF9D43E2 /* [CP] Copy Pods Resources */,
231 | );
232 | buildRules = (
233 | );
234 | dependencies = (
235 | );
236 | name = OKBluetooth_Example;
237 | productName = OKBluetooth;
238 | productReference = 6003F58A195388D20070C39A /* OKBluetooth_Example.app */;
239 | productType = "com.apple.product-type.application";
240 | };
241 | 6003F5AD195388D20070C39A /* OKBluetooth_Tests */ = {
242 | isa = PBXNativeTarget;
243 | buildConfigurationList = 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "OKBluetooth_Tests" */;
244 | buildPhases = (
245 | A4C0D38FE18A13739DA09554 /* [CP] Check Pods Manifest.lock */,
246 | 6003F5AA195388D20070C39A /* Sources */,
247 | 6003F5AB195388D20070C39A /* Frameworks */,
248 | 6003F5AC195388D20070C39A /* Resources */,
249 | );
250 | buildRules = (
251 | );
252 | dependencies = (
253 | 6003F5B4195388D20070C39A /* PBXTargetDependency */,
254 | );
255 | name = OKBluetooth_Tests;
256 | productName = OKBluetoothTests;
257 | productReference = 6003F5AE195388D20070C39A /* OKBluetooth_Tests.xctest */;
258 | productType = "com.apple.product-type.bundle.unit-test";
259 | };
260 | /* End PBXNativeTarget section */
261 |
262 | /* Begin PBXProject section */
263 | 6003F582195388D10070C39A /* Project object */ = {
264 | isa = PBXProject;
265 | attributes = {
266 | CLASSPREFIX = OK;
267 | LastUpgradeCheck = 0720;
268 | ORGANIZATIONNAME = deadvia;
269 | TargetAttributes = {
270 | 6003F589195388D20070C39A = {
271 | DevelopmentTeam = LDCR2454UZ;
272 | SystemCapabilities = {
273 | com.apple.BackgroundModes = {
274 | enabled = 1;
275 | };
276 | };
277 | };
278 | 6003F5AD195388D20070C39A = {
279 | TestTargetID = 6003F589195388D20070C39A;
280 | };
281 | };
282 | };
283 | buildConfigurationList = 6003F585195388D10070C39A /* Build configuration list for PBXProject "OKBluetooth" */;
284 | compatibilityVersion = "Xcode 3.2";
285 | developmentRegion = English;
286 | hasScannedForEncodings = 0;
287 | knownRegions = (
288 | en,
289 | Base,
290 | );
291 | mainGroup = 6003F581195388D10070C39A;
292 | productRefGroup = 6003F58B195388D20070C39A /* Products */;
293 | projectDirPath = "";
294 | projectRoot = "";
295 | targets = (
296 | 6003F589195388D20070C39A /* OKBluetooth_Example */,
297 | 6003F5AD195388D20070C39A /* OKBluetooth_Tests */,
298 | );
299 | };
300 | /* End PBXProject section */
301 |
302 | /* Begin PBXResourcesBuildPhase section */
303 | 6003F588195388D20070C39A /* Resources */ = {
304 | isa = PBXResourcesBuildPhase;
305 | buildActionMask = 2147483647;
306 | files = (
307 | 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */,
308 | 71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */,
309 | 6003F5A9195388D20070C39A /* Images.xcassets in Resources */,
310 | 6003F598195388D20070C39A /* InfoPlist.strings in Resources */,
311 | );
312 | runOnlyForDeploymentPostprocessing = 0;
313 | };
314 | 6003F5AC195388D20070C39A /* Resources */ = {
315 | isa = PBXResourcesBuildPhase;
316 | buildActionMask = 2147483647;
317 | files = (
318 | 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */,
319 | );
320 | runOnlyForDeploymentPostprocessing = 0;
321 | };
322 | /* End PBXResourcesBuildPhase section */
323 |
324 | /* Begin PBXShellScriptBuildPhase section */
325 | 010FDEE87BFE3EE4BF9D43E2 /* [CP] Copy Pods Resources */ = {
326 | isa = PBXShellScriptBuildPhase;
327 | buildActionMask = 2147483647;
328 | files = (
329 | );
330 | inputPaths = (
331 | "${SRCROOT}/Pods/Target Support Files/Pods-OKBluetooth_Example/Pods-OKBluetooth_Example-resources.sh",
332 | "${PODS_ROOT}/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle",
333 | );
334 | name = "[CP] Copy Pods Resources";
335 | outputPaths = (
336 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SVProgressHUD.bundle",
337 | );
338 | runOnlyForDeploymentPostprocessing = 0;
339 | shellPath = /bin/sh;
340 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-OKBluetooth_Example/Pods-OKBluetooth_Example-resources.sh\"\n";
341 | showEnvVarsInLog = 0;
342 | };
343 | 04803207D869908C02DB028F /* [CP] Check Pods Manifest.lock */ = {
344 | isa = PBXShellScriptBuildPhase;
345 | buildActionMask = 2147483647;
346 | files = (
347 | );
348 | inputPaths = (
349 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
350 | "${PODS_ROOT}/Manifest.lock",
351 | );
352 | name = "[CP] Check Pods Manifest.lock";
353 | outputPaths = (
354 | "$(DERIVED_FILE_DIR)/Pods-OKBluetooth_Example-checkManifestLockResult.txt",
355 | );
356 | runOnlyForDeploymentPostprocessing = 0;
357 | shellPath = /bin/sh;
358 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
359 | showEnvVarsInLog = 0;
360 | };
361 | A4C0D38FE18A13739DA09554 /* [CP] Check Pods Manifest.lock */ = {
362 | isa = PBXShellScriptBuildPhase;
363 | buildActionMask = 2147483647;
364 | files = (
365 | );
366 | inputPaths = (
367 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
368 | "${PODS_ROOT}/Manifest.lock",
369 | );
370 | name = "[CP] Check Pods Manifest.lock";
371 | outputPaths = (
372 | "$(DERIVED_FILE_DIR)/Pods-OKBluetooth_Tests-checkManifestLockResult.txt",
373 | );
374 | runOnlyForDeploymentPostprocessing = 0;
375 | shellPath = /bin/sh;
376 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
377 | showEnvVarsInLog = 0;
378 | };
379 | /* End PBXShellScriptBuildPhase section */
380 |
381 | /* Begin PBXSourcesBuildPhase section */
382 | 6003F586195388D20070C39A /* Sources */ = {
383 | isa = PBXSourcesBuildPhase;
384 | buildActionMask = 2147483647;
385 | files = (
386 | 6003F59E195388D20070C39A /* OKAppDelegate.m in Sources */,
387 | 6003F5A7195388D20070C39A /* OKViewController.m in Sources */,
388 | 416AA260210EBB5C005AC605 /* OKPeripheralViewController.m in Sources */,
389 | 416AA25D210EB5B7005AC605 /* OKPeripheralCell.m in Sources */,
390 | 41F85F13210B2220002DE259 /* OKListViewController.m in Sources */,
391 | 416AA263210EFC50005AC605 /* OKCharactersViewController.m in Sources */,
392 | 6003F59A195388D20070C39A /* main.m in Sources */,
393 | );
394 | runOnlyForDeploymentPostprocessing = 0;
395 | };
396 | 6003F5AA195388D20070C39A /* Sources */ = {
397 | isa = PBXSourcesBuildPhase;
398 | buildActionMask = 2147483647;
399 | files = (
400 | 6003F5BC195388D20070C39A /* Tests.m in Sources */,
401 | );
402 | runOnlyForDeploymentPostprocessing = 0;
403 | };
404 | /* End PBXSourcesBuildPhase section */
405 |
406 | /* Begin PBXTargetDependency section */
407 | 6003F5B4195388D20070C39A /* PBXTargetDependency */ = {
408 | isa = PBXTargetDependency;
409 | target = 6003F589195388D20070C39A /* OKBluetooth_Example */;
410 | targetProxy = 6003F5B3195388D20070C39A /* PBXContainerItemProxy */;
411 | };
412 | /* End PBXTargetDependency section */
413 |
414 | /* Begin PBXVariantGroup section */
415 | 6003F596195388D20070C39A /* InfoPlist.strings */ = {
416 | isa = PBXVariantGroup;
417 | children = (
418 | 6003F597195388D20070C39A /* en */,
419 | );
420 | name = InfoPlist.strings;
421 | sourceTree = "";
422 | };
423 | 6003F5B8195388D20070C39A /* InfoPlist.strings */ = {
424 | isa = PBXVariantGroup;
425 | children = (
426 | 6003F5B9195388D20070C39A /* en */,
427 | );
428 | name = InfoPlist.strings;
429 | sourceTree = "";
430 | };
431 | 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */ = {
432 | isa = PBXVariantGroup;
433 | children = (
434 | 71719F9E1E33DC2100824A3D /* Base */,
435 | );
436 | name = LaunchScreen.storyboard;
437 | sourceTree = "";
438 | };
439 | /* End PBXVariantGroup section */
440 |
441 | /* Begin XCBuildConfiguration section */
442 | 6003F5BD195388D20070C39A /* Debug */ = {
443 | isa = XCBuildConfiguration;
444 | buildSettings = {
445 | ALWAYS_SEARCH_USER_PATHS = NO;
446 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
447 | CLANG_CXX_LIBRARY = "libc++";
448 | CLANG_ENABLE_MODULES = YES;
449 | CLANG_ENABLE_OBJC_ARC = YES;
450 | CLANG_WARN_BOOL_CONVERSION = YES;
451 | CLANG_WARN_CONSTANT_CONVERSION = YES;
452 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
453 | CLANG_WARN_EMPTY_BODY = YES;
454 | CLANG_WARN_ENUM_CONVERSION = YES;
455 | CLANG_WARN_INT_CONVERSION = YES;
456 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
457 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
458 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
459 | COPY_PHASE_STRIP = NO;
460 | ENABLE_TESTABILITY = YES;
461 | GCC_C_LANGUAGE_STANDARD = gnu99;
462 | GCC_DYNAMIC_NO_PIC = NO;
463 | GCC_OPTIMIZATION_LEVEL = 0;
464 | GCC_PREPROCESSOR_DEFINITIONS = (
465 | "DEBUG=1",
466 | "$(inherited)",
467 | );
468 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
469 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
470 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
471 | GCC_WARN_UNDECLARED_SELECTOR = YES;
472 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
473 | GCC_WARN_UNUSED_FUNCTION = YES;
474 | GCC_WARN_UNUSED_VARIABLE = YES;
475 | IPHONEOS_DEPLOYMENT_TARGET = 9.3;
476 | ONLY_ACTIVE_ARCH = YES;
477 | SDKROOT = iphoneos;
478 | TARGETED_DEVICE_FAMILY = "1,2";
479 | };
480 | name = Debug;
481 | };
482 | 6003F5BE195388D20070C39A /* Release */ = {
483 | isa = XCBuildConfiguration;
484 | buildSettings = {
485 | ALWAYS_SEARCH_USER_PATHS = NO;
486 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
487 | CLANG_CXX_LIBRARY = "libc++";
488 | CLANG_ENABLE_MODULES = YES;
489 | CLANG_ENABLE_OBJC_ARC = YES;
490 | CLANG_WARN_BOOL_CONVERSION = YES;
491 | CLANG_WARN_CONSTANT_CONVERSION = YES;
492 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
493 | CLANG_WARN_EMPTY_BODY = YES;
494 | CLANG_WARN_ENUM_CONVERSION = YES;
495 | CLANG_WARN_INT_CONVERSION = YES;
496 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
497 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
498 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
499 | COPY_PHASE_STRIP = YES;
500 | ENABLE_NS_ASSERTIONS = NO;
501 | GCC_C_LANGUAGE_STANDARD = gnu99;
502 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
503 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
504 | GCC_WARN_UNDECLARED_SELECTOR = YES;
505 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
506 | GCC_WARN_UNUSED_FUNCTION = YES;
507 | GCC_WARN_UNUSED_VARIABLE = YES;
508 | IPHONEOS_DEPLOYMENT_TARGET = 9.3;
509 | SDKROOT = iphoneos;
510 | TARGETED_DEVICE_FAMILY = "1,2";
511 | VALIDATE_PRODUCT = YES;
512 | };
513 | name = Release;
514 | };
515 | 6003F5C0195388D20070C39A /* Debug */ = {
516 | isa = XCBuildConfiguration;
517 | baseConfigurationReference = B99FCCBC6489B0D7E582E02A /* Pods-OKBluetooth_Example.debug.xcconfig */;
518 | buildSettings = {
519 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
520 | DEVELOPMENT_TEAM = LDCR2454UZ;
521 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
522 | GCC_PREFIX_HEADER = "OKBluetooth/OKBluetooth-Prefix.pch";
523 | INFOPLIST_FILE = "OKBluetooth/OKBluetooth-Info.plist";
524 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
525 | MODULE_NAME = ExampleApp;
526 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
527 | PRODUCT_NAME = "$(TARGET_NAME)";
528 | TARGETED_DEVICE_FAMILY = 1;
529 | WRAPPER_EXTENSION = app;
530 | };
531 | name = Debug;
532 | };
533 | 6003F5C1195388D20070C39A /* Release */ = {
534 | isa = XCBuildConfiguration;
535 | baseConfigurationReference = 273A1D9CE48D6391BB4834CC /* Pods-OKBluetooth_Example.release.xcconfig */;
536 | buildSettings = {
537 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
538 | DEVELOPMENT_TEAM = LDCR2454UZ;
539 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
540 | GCC_PREFIX_HEADER = "OKBluetooth/OKBluetooth-Prefix.pch";
541 | INFOPLIST_FILE = "OKBluetooth/OKBluetooth-Info.plist";
542 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
543 | MODULE_NAME = ExampleApp;
544 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
545 | PRODUCT_NAME = "$(TARGET_NAME)";
546 | TARGETED_DEVICE_FAMILY = 1;
547 | WRAPPER_EXTENSION = app;
548 | };
549 | name = Release;
550 | };
551 | 6003F5C3195388D20070C39A /* Debug */ = {
552 | isa = XCBuildConfiguration;
553 | baseConfigurationReference = 1B26FEF3BC1D8695FA5E6EC3 /* Pods-OKBluetooth_Tests.debug.xcconfig */;
554 | buildSettings = {
555 | BUNDLE_LOADER = "$(TEST_HOST)";
556 | FRAMEWORK_SEARCH_PATHS = (
557 | "$(SDKROOT)/Developer/Library/Frameworks",
558 | "$(inherited)",
559 | "$(DEVELOPER_FRAMEWORKS_DIR)",
560 | );
561 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
562 | GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch";
563 | GCC_PREPROCESSOR_DEFINITIONS = (
564 | "DEBUG=1",
565 | "$(inherited)",
566 | );
567 | INFOPLIST_FILE = "Tests/Tests-Info.plist";
568 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
569 | PRODUCT_NAME = "$(TARGET_NAME)";
570 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OKBluetooth_Example.app/OKBluetooth_Example";
571 | WRAPPER_EXTENSION = xctest;
572 | };
573 | name = Debug;
574 | };
575 | 6003F5C4195388D20070C39A /* Release */ = {
576 | isa = XCBuildConfiguration;
577 | baseConfigurationReference = EDD65610381486BEEAA53A58 /* Pods-OKBluetooth_Tests.release.xcconfig */;
578 | buildSettings = {
579 | BUNDLE_LOADER = "$(TEST_HOST)";
580 | FRAMEWORK_SEARCH_PATHS = (
581 | "$(SDKROOT)/Developer/Library/Frameworks",
582 | "$(inherited)",
583 | "$(DEVELOPER_FRAMEWORKS_DIR)",
584 | );
585 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
586 | GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch";
587 | INFOPLIST_FILE = "Tests/Tests-Info.plist";
588 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
589 | PRODUCT_NAME = "$(TARGET_NAME)";
590 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OKBluetooth_Example.app/OKBluetooth_Example";
591 | WRAPPER_EXTENSION = xctest;
592 | };
593 | name = Release;
594 | };
595 | /* End XCBuildConfiguration section */
596 |
597 | /* Begin XCConfigurationList section */
598 | 6003F585195388D10070C39A /* Build configuration list for PBXProject "OKBluetooth" */ = {
599 | isa = XCConfigurationList;
600 | buildConfigurations = (
601 | 6003F5BD195388D20070C39A /* Debug */,
602 | 6003F5BE195388D20070C39A /* Release */,
603 | );
604 | defaultConfigurationIsVisible = 0;
605 | defaultConfigurationName = Release;
606 | };
607 | 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "OKBluetooth_Example" */ = {
608 | isa = XCConfigurationList;
609 | buildConfigurations = (
610 | 6003F5C0195388D20070C39A /* Debug */,
611 | 6003F5C1195388D20070C39A /* Release */,
612 | );
613 | defaultConfigurationIsVisible = 0;
614 | defaultConfigurationName = Release;
615 | };
616 | 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "OKBluetooth_Tests" */ = {
617 | isa = XCConfigurationList;
618 | buildConfigurations = (
619 | 6003F5C3195388D20070C39A /* Debug */,
620 | 6003F5C4195388D20070C39A /* Release */,
621 | );
622 | defaultConfigurationIsVisible = 0;
623 | defaultConfigurationName = Release;
624 | };
625 | /* End XCConfigurationList section */
626 | };
627 | rootObject = 6003F582195388D10070C39A /* Project object */;
628 | }
629 |
--------------------------------------------------------------------------------
/Example/OKBluetooth.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/OKBluetooth.xcodeproj/xcshareddata/xcschemes/OKBluetooth-Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
85 |
91 |
92 |
93 |
94 |
96 |
97 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
35 |
41 |
47 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
114 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
158 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/OKAppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // OKAppDelegate.h
3 | // OKBluetooth
4 | //
5 | // Created by deadvia on 07/25/2018.
6 | // Copyright (c) 2018 deadvia. All rights reserved.
7 | //
8 |
9 | @import UIKit;
10 |
11 | @interface OKAppDelegate : UIResponder
12 |
13 | @property (strong, nonatomic) UIWindow *window;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/OKAppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // OKAppDelegate.m
3 | // OKBluetooth
4 | //
5 | // Created by deadvia on 07/25/2018.
6 | // Copyright (c) 2018 deadvia. All rights reserved.
7 | //
8 |
9 | #import "OKAppDelegate.h"
10 | #import
11 | #import
12 |
13 | @implementation OKAppDelegate
14 |
15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
16 | {
17 | // Override point for customization after application launch.
18 | NSArray *centralManagerIdentifiers = launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey];
19 | if (centralManagerIdentifiers) {
20 | [[OKCentralManager sharedInstance] retrievePeripheralsWithIdentifiers:centralManagerIdentifiers];
21 | }
22 |
23 | [SVProgressHUD setDefaultStyle:SVProgressHUDStyleDark];
24 | [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeBlack];
25 | [SVProgressHUD setMinimumSize:CGSizeMake(100, 100)];
26 | [SVProgressHUD setHapticsEnabled:YES];
27 | [SVProgressHUD setMinimumDismissTimeInterval:2.0];
28 |
29 | return YES;
30 | }
31 |
32 | - (void)applicationWillResignActive:(UIApplication *)application
33 | {
34 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
35 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
36 | }
37 |
38 | - (void)applicationDidEnterBackground:(UIApplication *)application
39 | {
40 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
41 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
42 | }
43 |
44 | - (void)applicationWillEnterForeground:(UIApplication *)application
45 | {
46 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
47 | }
48 |
49 | - (void)applicationDidBecomeActive:(UIApplication *)application
50 | {
51 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
52 | }
53 |
54 | - (void)applicationWillTerminate:(UIApplication *)application
55 | {
56 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
57 | }
58 |
59 | @end
60 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/OKBluetooth-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | ${PRODUCT_NAME}
9 | CFBundleExecutable
10 | ${EXECUTABLE_NAME}
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | ${PRODUCT_NAME}
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1.0
25 | LSRequiresIPhoneOS
26 |
27 | UIBackgroundModes
28 |
29 | bluetooth-central
30 | bluetooth-peripheral
31 | external-accessory
32 |
33 | UILaunchStoryboardName
34 | LaunchScreen
35 | UIMainStoryboardFile
36 | Main
37 | UIRequiredDeviceCapabilities
38 |
39 | armv7
40 |
41 | UISupportedInterfaceOrientations
42 |
43 | UIInterfaceOrientationPortrait
44 | UIInterfaceOrientationLandscapeLeft
45 | UIInterfaceOrientationLandscapeRight
46 |
47 | UISupportedInterfaceOrientations~ipad
48 |
49 | UIInterfaceOrientationPortrait
50 | UIInterfaceOrientationPortraitUpsideDown
51 | UIInterfaceOrientationLandscapeLeft
52 | UIInterfaceOrientationLandscapeRight
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/OKBluetooth-Prefix.pch:
--------------------------------------------------------------------------------
1 | //
2 | // Prefix header
3 | //
4 | // The contents of this file are implicitly included at the beginning of every source file.
5 | //
6 |
7 | #import
8 |
9 | #ifndef __IPHONE_5_0
10 | #warning "This project uses features only available in iOS SDK 5.0 and later."
11 | #endif
12 |
13 | #ifdef __OBJC__
14 | @import UIKit;
15 | @import Foundation;
16 | #endif
17 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/OKCharactersViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // OKCharactersViewController.h
3 | // OKBluetooth_Example
4 | //
5 | // Created by yuhanle on 2018/7/30.
6 | // Copyright © 2018年 deadvia. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @class OKService;
12 | @interface OKCharactersViewController : UITableViewController
13 |
14 | @property (nonatomic, strong) OKService *service;
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/OKCharactersViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // OKCharactersViewController.m
3 | // OKBluetooth_Example
4 | //
5 | // Created by yuhanle on 2018/7/30.
6 | // Copyright © 2018年 deadvia. All rights reserved.
7 | //
8 |
9 | #import "OKCharactersViewController.h"
10 | #import
11 |
12 | //服务
13 | NSString * const kDevControllServiceUUIDString = @"0000FEE9-0000-1000-8000-00805F9B34FB";
14 | NSString * const kDevOTAServiceUUIDString = @"FEE8";
15 |
16 | NSString * const kDevDeviceWriteCharacteristicUUIDString = @"D44BC439-ABFD-45A2-B575-925416129600";
17 | NSString * const kDevDeviceReadCharacteristicUUIDString = @"D44BC439-ABFD-45A2-B575-925416129601";
18 | NSString * const kDevDeviceVersionCharacteristicUUIDString = @"D44BC439-ABFD-45A2-B575-925416129602";
19 | NSString * const kDevDeviceInfoCharacteristicUUIDString = @"D44BC439-ABFD-45A2-B575-925416129603";
20 | NSString * const kDevDeviceStateCharacteristicUUIDString = @"D44BC439-ABFD-45A2-B575-925416129604";
21 | NSString * const kDevDeviceConnectInfoCharacteristicUUIDString = @"D44BC439-ABFD-45A2-B575-925416129605";
22 |
23 | @interface OKCharactersViewController ()
24 |
25 | @property (strong, nonatomic) NSMutableArray *okItems;
26 | @property (strong, nonatomic) RACDisposable *notifyRACDisposable;
27 |
28 | @end
29 |
30 | @implementation OKCharactersViewController
31 |
32 | - (void)dealloc {
33 | [self.notifyRACDisposable dispose];
34 | }
35 |
36 | #pragma mark - Lazy load
37 | - (NSMutableArray *)okItems {
38 | if (!_okItems) {
39 | _okItems = [[NSMutableArray alloc] init];
40 | }
41 | return _okItems;
42 | }
43 |
44 | #pragma mark - Life Cycle
45 | - (void)viewDidLoad {
46 | [super viewDidLoad];
47 |
48 | [[self.service.discoverCharacteristicsCommand execute:@[]] subscribeNext:^(OKService *x) {
49 | [self.okItems removeAllObjects];
50 | [self.okItems addObjectsFromArray:x.characteristics];
51 | } error:^(NSError * _Nullable error) {
52 | NSLog(@"%@", error);
53 | } completed:^{
54 | dispatch_async(dispatch_get_main_queue(), ^{
55 | [self.tableView reloadData];
56 | });
57 | }];
58 | }
59 |
60 | - (void)didReceiveMemoryWarning {
61 | [super didReceiveMemoryWarning];
62 | // Dispose of any resources that can be recreated.
63 | }
64 |
65 | #pragma mark - Table view data source
66 |
67 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
68 | return 1;
69 | }
70 |
71 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
72 | return self.okItems.count;
73 | }
74 |
75 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
76 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"characteristic" forIndexPath:indexPath];
77 | OKCharacteristic *okph = [self.okItems objectAtIndex:indexPath.row];
78 | cell.textLabel.text = [NSString stringWithFormat:@"%@", okph];
79 | cell.detailTextLabel.text = okph.UUIDString;
80 | return cell;
81 | }
82 |
83 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
84 | [tableView deselectRowAtIndexPath:indexPath animated:YES];
85 |
86 | OKCharacteristic *okph = [self.okItems objectAtIndex:indexPath.row];
87 | if ([[okph.UUIDString uppercaseString] isEqualToString:kDevDeviceWriteCharacteristicUUIDString]) {
88 | OKWriteValueModel *model = [[OKWriteValueModel alloc] init];
89 | model.type = CBCharacteristicWriteWithoutResponse;
90 |
91 | Byte byte[] = {1, 3};
92 | model.data = [NSData dataWithBytes:byte length:2];
93 | [[okph.writeValueCommand execute:model] subscribeNext:^(id _Nullable x) {
94 | NSLog(@"writeValue: %@", x);
95 | } error:^(NSError * _Nullable error) {
96 | NSLog(@"writeValue: %@", error);
97 | } completed:^{
98 | NSLog(@"writeSuccess");
99 | }];
100 | }
101 |
102 | if ([[okph.UUIDString uppercaseString] isEqualToString:kDevDeviceReadCharacteristicUUIDString]) {
103 | [[okph.readValueCommand execute:@0] subscribeNext:^(id _Nullable x) {
104 | NSLog(@"readValue: %@", x);
105 | }];
106 | }
107 |
108 | if ([[okph.UUIDString uppercaseString] isEqualToString:kDevDeviceReadCharacteristicUUIDString]) {
109 | [[okph.notifyValueCommand execute:@YES] subscribeNext:^(id _Nullable x) {
110 | NSLog(@"notify: %@", x);
111 | } error:^(NSError * _Nullable error) {
112 | NSLog(@"notifyError: %@", error);
113 | }];
114 | }
115 | }
116 |
117 | /*
118 | #pragma mark - Navigation
119 |
120 | // In a storyboard-based application, you will often want to do a little preparation before navigation
121 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
122 | // Get the new view controller using [segue destinationViewController].
123 | // Pass the selected object to the new view controller.
124 | }
125 | */
126 |
127 | @end
128 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/OKListViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // OKListViewController.h
3 | // OKBluetooth_Example
4 | //
5 | // Created by yuhanle on 2018/7/27.
6 | // Copyright © 2018年 deadvia. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface OKListViewController : UITableViewController
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/OKListViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // OKListViewController.m
3 | // OKBluetooth_Example
4 | //
5 | // Created by yuhanle on 2018/7/27.
6 | // Copyright © 2018年 deadvia. All rights reserved.
7 | //
8 |
9 | #import "OKListViewController.h"
10 | #import
11 | #import
12 | #import "OKPeripheralCell.h"
13 | #import "OKPeripheralViewController.h"
14 |
15 | @interface OKListViewController ()
16 |
17 | @property (assign, nonatomic) BOOL hasAppear;
18 | @property (strong, nonatomic) RACDisposable *dsp;
19 | @property (strong, nonatomic) NSMutableArray *okItems;
20 |
21 | @property (strong, nonatomic) NSIndexPath *selectedIndexPath;
22 |
23 | @end
24 |
25 | @implementation OKListViewController
26 |
27 | - (void)dealloc {
28 | [self.dsp dispose];
29 | }
30 |
31 | - (IBAction)_action_refresh:(UIBarButtonItem *)sender {
32 | [self.okItems removeAllObjects];
33 | [self.tableView reloadData];
34 |
35 | OKScanModel *input = [[OKScanModel alloc] initModelWithServiceUUIDs:nil options:nil aScanInterval:10];
36 |
37 | @weakify(self);
38 | self.dsp = [[[OKCentralManager sharedInstance].scanForPeripheralsCommand execute:input] subscribeNext:^(OKPeripheral *peripheral) {
39 | @strongify(self);
40 | @synchronized (self.okItems) {
41 | [self.okItems removeAllObjects];
42 | [self.okItems addObjectsFromArray:[OKCentralManager sharedInstance].peripherals];
43 | dispatch_async(dispatch_get_main_queue(), ^{
44 | [self.tableView reloadData];
45 | });
46 | }
47 | } error:^(NSError * _Nullable error) {
48 | NSLog(@"%@", error);
49 | dispatch_async(dispatch_get_main_queue(), ^{
50 | if (error.code == 4) {
51 | [SVProgressHUD showErrorWithStatus:@"蓝牙未开启"];
52 | }else if (error.code == kOKUtilsScanTimeoutErrorCode) {
53 | [SVProgressHUD showErrorWithStatus:@"扫描超时"];
54 | }
55 | });
56 | }];
57 | }
58 |
59 | #pragma mark - Lazy load
60 | - (NSMutableArray *)okItems {
61 | if (!_okItems) {
62 | _okItems = [[NSMutableArray alloc] init];
63 | }
64 | return _okItems;
65 | }
66 |
67 | #pragma mark - Life Cycle
68 | - (void)viewDidLoad {
69 | [super viewDidLoad];
70 |
71 | [OKCentralManager sharedInstance].peripheralsCountToStop = 30;
72 | [[OKCentralManager sharedInstance].scanForPeripheralsCommand.executing subscribeNext:^(NSNumber * _Nullable x) {
73 | if (x.boolValue) {
74 | [SVProgressHUD showWithStatus:@"搜索中"];
75 | } else {
76 | [SVProgressHUD dismiss];
77 | }
78 | }];
79 |
80 | @weakify(self);
81 | [[OKCentralManager sharedInstance].centralManagerStateConnection.signal subscribeNext:^(NSNumber *x) {
82 | @strongify(self);
83 | if (x.integerValue == CBManagerStatePoweredOn && ![OKCentralManager sharedInstance].isScanning) {
84 | [self _action_refresh:nil];
85 | }
86 | }];
87 | }
88 |
89 | - (void)viewDidAppear:(BOOL)animated {
90 | [super viewDidAppear:animated];
91 |
92 | if (!self.hasAppear) {
93 | [self _action_refresh:nil];
94 | }
95 | self.hasAppear = YES;
96 | }
97 |
98 | - (void)didReceiveMemoryWarning {
99 | [super didReceiveMemoryWarning];
100 | // Dispose of any resources that can be recreated.
101 | }
102 |
103 | #pragma mark - Table view data source
104 |
105 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
106 | return 1;
107 | }
108 |
109 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
110 | return self.okItems.count;
111 | }
112 |
113 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
114 | OKPeripheralCell *cell = [tableView dequeueReusableCellWithIdentifier:@"OKPeripheralCell" forIndexPath:indexPath];
115 | OKPeripheral *okph = [self.okItems objectAtIndex:indexPath.row];
116 | cell.peripheral = okph;
117 | return cell;
118 | }
119 |
120 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
121 | [tableView deselectRowAtIndexPath:indexPath animated:YES];
122 | self.selectedIndexPath = indexPath;
123 |
124 | [self performSegueWithIdentifier:@"SeePeripheralSegue" sender:nil];
125 | }
126 |
127 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
128 | return 100;
129 | }
130 |
131 | #pragma mark - Navigation
132 |
133 | // In a storyboard-based application, you will often want to do a little preparation before navigation
134 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
135 | // Get the new view controller using [segue destinationViewController].
136 | // Pass the selected object to the new view controller.
137 |
138 | if ([segue.destinationViewController isKindOfClass:[OKPeripheralViewController class]]) {
139 | ((OKPeripheralViewController *)segue.destinationViewController).okph = [self.okItems objectAtIndex:self.selectedIndexPath.row];
140 | self.selectedIndexPath = nil;
141 | }
142 | }
143 |
144 | @end
145 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/OKPeripheralCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // OKPeripheralCell.h
3 | // OKBluetooth_Example
4 | //
5 | // Created by yuhanle on 2018/7/30.
6 | // Copyright © 2018年 deadvia. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @class OKPeripheral;
12 |
13 | @interface OKPeripheralCell : UITableViewCell
14 |
15 | @property (weak, nonatomic) IBOutlet UILabel *uuidLabel;
16 | @property (weak, nonatomic) IBOutlet UILabel *nameLabel;
17 | @property (weak, nonatomic) IBOutlet UIButton *clickBtn;
18 | @property (weak, nonatomic) IBOutlet UILabel *rssiLabel;
19 |
20 | @property (strong, nonatomic) OKPeripheral *peripheral;
21 |
22 | @end
23 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/OKPeripheralCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // OKPeripheralCell.m
3 | // OKBluetooth_Example
4 | //
5 | // Created by yuhanle on 2018/7/30.
6 | // Copyright © 2018年 deadvia. All rights reserved.
7 | //
8 |
9 | #import "OKPeripheralCell.h"
10 | #import
11 | #import
12 |
13 | @implementation OKPeripheralCell
14 |
15 | - (void)awakeFromNib {
16 | [super awakeFromNib];
17 | // Initialization code
18 |
19 | @weakify(self);
20 | [[_clickBtn rac_signalForControlEvents:UIControlEventTouchUpInside]
21 | subscribeNext:^(__kindof UIControl * _Nullable x) {
22 | @strongify(self);
23 | if (self.peripheral.isConnected) {
24 | [[self.peripheral.disConnectCommand execute:@1] subscribeNext:^(id _Nullable x) {
25 |
26 | } error:^(NSError * _Nullable error) {
27 | NSLog(@"%@", error);
28 | } completed:^{
29 | dispatch_async(dispatch_get_main_queue(), ^{
30 | self.peripheral = _peripheral;
31 | });
32 | }];
33 | }else {
34 | [SVProgressHUD showWithStatus:@"连接中"];
35 | [[self.peripheral.connectCommand execute:@30] subscribeNext:^(id _Nullable x) {
36 |
37 | } error:^(NSError * _Nullable error) {
38 | NSLog(@"%@", error);
39 | dispatch_async(dispatch_get_main_queue(), ^{
40 | [SVProgressHUD dismiss];
41 | });
42 | } completed:^{
43 | dispatch_async(dispatch_get_main_queue(), ^{
44 | self.peripheral = _peripheral;
45 | [SVProgressHUD dismiss];
46 | });
47 | }];
48 | }
49 | }];
50 | }
51 |
52 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated {
53 | [super setSelected:selected animated:animated];
54 |
55 | // Configure the view for the selected state
56 | }
57 |
58 | - (void)setPeripheral:(OKPeripheral *)peripheral {
59 | _peripheral = peripheral;
60 |
61 | _nameLabel.text = [NSString stringWithFormat:@"name: %@", peripheral.name.length ? peripheral.name : @"NONAME"];
62 | _uuidLabel.text = [NSString stringWithFormat:@"uuid: %@", peripheral.UUIDString];
63 | _rssiLabel.text = [NSString stringWithFormat:@"RSSI: %@", @(peripheral.RSSI)];
64 |
65 | if (peripheral.isConnected) {
66 | [_clickBtn setTitle:@"断开连接" forState:UIControlStateNormal];
67 | }else {
68 | [_clickBtn setTitle:@"连接" forState:UIControlStateNormal];
69 | }
70 | }
71 |
72 | @end
73 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/OKPeripheralViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // OKPeripheralViewController.h
3 | // OKBluetooth_Example
4 | //
5 | // Created by yuhanle on 2018/7/30.
6 | // Copyright © 2018年 deadvia. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 | @interface OKPeripheralViewController : UITableViewController
13 |
14 | @property (nonatomic, strong) OKPeripheral *okph;
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/OKPeripheralViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // OKPeripheralViewController.m
3 | // OKBluetooth_Example
4 | //
5 | // Created by yuhanle on 2018/7/30.
6 | // Copyright © 2018年 deadvia. All rights reserved.
7 | //
8 |
9 | #import "OKPeripheralViewController.h"
10 | #import
11 | #import "OKCharactersViewController.h"
12 |
13 | @interface OKPeripheralViewController ()
14 |
15 | @property (strong, nonatomic) NSMutableArray *okItems;
16 | @property (strong, nonatomic) NSIndexPath *selectedIndexPath;
17 |
18 | @end
19 |
20 | @implementation OKPeripheralViewController
21 |
22 | #pragma mark - Lazy load
23 | - (NSMutableArray *)okItems {
24 | if (!_okItems) {
25 | _okItems = [[NSMutableArray alloc] init];
26 | }
27 | return _okItems;
28 | }
29 |
30 | #pragma mark - Life Cycle
31 | - (void)viewDidLoad {
32 | [super viewDidLoad];
33 | // Do any additional setup after loading the view.
34 |
35 | self.title = self.okph.name;
36 | [[self.okph.discoverServicesCommand execute:@[]] subscribeNext:^(OKPeripheral *x) {
37 | [self.okItems removeAllObjects];
38 | [self.okItems addObjectsFromArray:x.services];
39 | } error:^(NSError * _Nullable error) {
40 | NSLog(@"%@", error);
41 | } completed:^{
42 | dispatch_async(dispatch_get_main_queue(), ^{
43 | [self.tableView reloadData];
44 | });
45 | }];
46 | }
47 |
48 | - (void)didReceiveMemoryWarning {
49 | [super didReceiveMemoryWarning];
50 | // Dispose of any resources that can be recreated.
51 | }
52 |
53 | #pragma mark - Table view data source
54 |
55 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
56 | return 1;
57 | }
58 |
59 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
60 | return self.okItems.count;
61 | }
62 |
63 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
64 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"peripheral" forIndexPath:indexPath];
65 | OKService *okph = [self.okItems objectAtIndex:indexPath.row];
66 | cell.textLabel.text = [NSString stringWithFormat:@"%@", okph];
67 | cell.detailTextLabel.text = okph.UUIDString;
68 | return cell;
69 | }
70 |
71 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
72 | [tableView deselectRowAtIndexPath:indexPath animated:YES];
73 | self.selectedIndexPath = indexPath;
74 |
75 | [self performSegueWithIdentifier:@"characteristic" sender:nil];
76 | }
77 |
78 | #pragma mark - Navigation
79 |
80 | // In a storyboard-based application, you will often want to do a little preparation before navigation
81 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
82 | // Get the new view controller using [segue destinationViewController].
83 | // Pass the selected object to the new view controller.
84 | if ([segue.destinationViewController isKindOfClass:[OKCharactersViewController class]]) {
85 | ((OKCharactersViewController *)segue.destinationViewController).service = [self.okItems objectAtIndex:self.selectedIndexPath.row];
86 | self.selectedIndexPath = nil;
87 | }
88 | }
89 |
90 | @end
91 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/OKViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // OKViewController.h
3 | // OKBluetooth
4 | //
5 | // Created by deadvia on 07/25/2018.
6 | // Copyright (c) 2018 deadvia. All rights reserved.
7 | //
8 |
9 | @import UIKit;
10 |
11 | @interface OKViewController : UIViewController
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/OKViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // OKViewController.m
3 | // OKBluetooth
4 | //
5 | // Created by deadvia on 07/25/2018.
6 | // Copyright (c) 2018 deadvia. All rights reserved.
7 | //
8 |
9 | #import "OKViewController.h"
10 | #import
11 |
12 | @interface OKViewController ()
13 |
14 | @property (weak, nonatomic) IBOutlet UIButton *scanServiceBtn;
15 | @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *okActivityView;
16 |
17 | @end
18 |
19 | @implementation OKViewController
20 |
21 | #pragma mark - Life Cycle
22 | - (void)viewDidLoad
23 | {
24 | [super viewDidLoad];
25 | // Do any additional setup after loading the view, typically from a nib.
26 |
27 | @weakify(self);
28 | [[OKCentralManager sharedInstance].scanForPeripheralsCommand.executing subscribeNext:^(NSNumber * _Nullable x) {
29 | @strongify(self);
30 | if (x.boolValue) {
31 | [self.okActivityView startAnimating];
32 | } else {
33 | [self.okActivityView stopAnimating];
34 | }
35 | }];
36 |
37 | [[[OKCentralManager sharedInstance].centralManagerStateConnection.signal distinctUntilChanged] subscribeNext:^(id _Nullable x) {
38 | NSLog(@"\n%@ %@", x, [[OKCentralManager sharedInstance] centralNotReadyReason]);
39 | }];
40 |
41 | [[OKCentralManager sharedInstance].connectPeripheralConnection.signal subscribeNext:^(id _Nullable x) {
42 | NSLog(@"Connection info: %@", x);
43 | }];
44 | }
45 |
46 | - (void)didReceiveMemoryWarning
47 | {
48 | [super didReceiveMemoryWarning];
49 | // Dispose of any resources that can be recreated.
50 | }
51 |
52 | @end
53 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/Example/OKBluetooth/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // OKBluetooth
4 | //
5 | // Created by deadvia on 07/25/2018.
6 | // Copyright (c) 2018 deadvia. All rights reserved.
7 | //
8 |
9 | @import UIKit;
10 | #import "OKAppDelegate.h"
11 |
12 | int main(int argc, char * argv[])
13 | {
14 | @autoreleasepool {
15 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([OKAppDelegate class]));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '8.0'
2 | inhibit_all_warnings!
3 |
4 | target 'OKBluetooth_Example' do
5 | pod 'OKBluetooth', :path => '../'
6 | pod 'SVProgressHUD', '2.2.5'
7 |
8 | target 'OKBluetooth_Tests' do
9 | inherit! :search_paths
10 |
11 | pod 'Specta'
12 | pod 'Expecta'
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/Example/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Expecta (1.0.6)
3 | - OKBluetooth (1.0.0):
4 | - ReactiveObjC (= 3.1.0)
5 | - ReactiveObjC (3.1.0)
6 | - Specta (1.0.7)
7 | - SVProgressHUD (2.2.5)
8 |
9 | DEPENDENCIES:
10 | - Expecta
11 | - OKBluetooth (from `../`)
12 | - Specta
13 | - SVProgressHUD (= 2.2.5)
14 |
15 | SPEC REPOS:
16 | https://github.com/cocoapods/specs.git:
17 | - Expecta
18 | - ReactiveObjC
19 | - Specta
20 | - SVProgressHUD
21 |
22 | EXTERNAL SOURCES:
23 | OKBluetooth:
24 | :path: "../"
25 |
26 | SPEC CHECKSUMS:
27 | Expecta: 3b6bd90a64b9a1dcb0b70aa0e10a7f8f631667d5
28 | OKBluetooth: 827d125134e812bba07fc838ec3acf5cb04f46a4
29 | ReactiveObjC: 2a38ea15335de4119d8b17caf1db1484f61db902
30 | Specta: 3e1bd89c3517421982dc4d1c992503e48bd5fe66
31 | SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6
32 |
33 | PODFILE CHECKSUM: 741bbd7aa1d066f2f95ac4025738736568790610
34 |
35 | COCOAPODS: 1.5.3
36 |
--------------------------------------------------------------------------------
/Example/Tests/Tests-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundlePackageType
14 | BNDL
15 | CFBundleShortVersionString
16 | 1.0
17 | CFBundleSignature
18 | ????
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Example/Tests/Tests-Prefix.pch:
--------------------------------------------------------------------------------
1 | // The contents of this file are implicitly included at the beginning of every test case source file.
2 |
3 | #ifdef __OBJC__
4 |
5 | @import Specta;
6 | @import Expecta;
7 |
8 | #endif
9 |
--------------------------------------------------------------------------------
/Example/Tests/Tests.m:
--------------------------------------------------------------------------------
1 | //
2 | // OKBluetoothTests.m
3 | // OKBluetoothTests
4 | //
5 | // Created by deadvia on 07/25/2018.
6 | // Copyright (c) 2018 deadvia. All rights reserved.
7 | //
8 |
9 | // https://github.com/Specta/Specta
10 |
11 | SpecBegin(InitialSpecs)
12 |
13 | describe(@"these will fail", ^{
14 |
15 | it(@"can do maths", ^{
16 | expect(1).to.equal(2);
17 | });
18 |
19 | it(@"can read", ^{
20 | expect(@"number").to.equal(@"string");
21 | });
22 |
23 | it(@"will wait for 10 seconds and fail", ^{
24 | waitUntil(^(DoneCallback done) {
25 |
26 | });
27 | });
28 | });
29 |
30 | describe(@"these will pass", ^{
31 |
32 | it(@"can do maths", ^{
33 | expect(1).beLessThan(23);
34 | });
35 |
36 | it(@"can read", ^{
37 | expect(@"team").toNot.contain(@"I");
38 | });
39 |
40 | it(@"will wait and succeed", ^{
41 | waitUntil(^(DoneCallback done) {
42 | done();
43 | });
44 | });
45 | });
46 |
47 | SpecEnd
48 |
49 |
--------------------------------------------------------------------------------
/Example/Tests/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018 yuhanle
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/OKBluetooth.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'OKBluetooth'
3 | s.version = '1.0.0'
4 | s.summary = 'Bluetooth library using ReactiveCocoa on ios.'
5 |
6 | s.description = <<-DESC
7 | The easiest way to use Bluetooth (BLE) on ios, using ReactiveCocoa.
8 | DESC
9 |
10 | s.homepage = 'https://github.com/latehorse/OKBluetooth'
11 | s.license = { :type => 'MIT', :file => 'LICENSE' }
12 | s.author = { 'yuhanle' => 'deadvia@gmail.com' }
13 | s.source = { :git => 'https://github.com/latehorse/OKBluetooth.git', :tag => s.version.to_s }
14 | s.social_media_url = 'https://twitter.com/yuhanle'
15 |
16 | s.ios.deployment_target = '7.0'
17 |
18 | s.source_files = 'OKBluetooth/Classes/**/*'
19 | s.frameworks = 'CoreBluetooth'
20 | s.dependency 'ReactiveObjC', '3.1.0'
21 | end
22 |
--------------------------------------------------------------------------------
/OKBluetooth/Assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latehorse/OKBluetooth/be258d4fd0b3137a6aa960a5df58a7de687d1b53/OKBluetooth/Assets/.gitkeep
--------------------------------------------------------------------------------
/OKBluetooth/Classes/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/latehorse/OKBluetooth/be258d4fd0b3137a6aa960a5df58a7de687d1b53/OKBluetooth/Classes/.gitkeep
--------------------------------------------------------------------------------
/OKBluetooth/Classes/CBUUID+StringExtraction.h:
--------------------------------------------------------------------------------
1 | //
2 | // CBUUID+StringExtraction.h
3 | // OKBluetooth
4 | //
5 | // Created by yuhanle on 2018/7/27.
6 | //
7 |
8 | #if TARGET_OS_IPHONE
9 | #import
10 | #elif TARGET_OS_MAC
11 | #import
12 | #endif
13 |
14 | @interface CBUUID (StringExtraction)
15 |
16 | /**
17 | * Converts 16bit and 128bit CBUUID to NSString representation
18 | * @return NSString representation of CBUUID
19 | */
20 | - (NSString *)representativeString;
21 |
22 | @end
23 |
--------------------------------------------------------------------------------
/OKBluetooth/Classes/CBUUID+StringExtraction.m:
--------------------------------------------------------------------------------
1 | //
2 | // CBUUID+StringExtraction.m
3 | // OKBluetooth
4 | //
5 | // Created by yuhanle on 2018/7/27.
6 | //
7 |
8 | #import "CBUUID+StringExtraction.h"
9 |
10 | @implementation CBUUID (StringExtraction)
11 |
12 | /*----------------------------------------------------*/
13 | #pragma mark - Helper Methods -
14 | /*----------------------------------------------------*/
15 |
16 | - (NSString *)representativeString
17 | {
18 | NSData *data = [self data];
19 |
20 | NSUInteger bytesToConvert = [data length];
21 | const unsigned char *uuidBytes = [data bytes];
22 | NSMutableString *outputString = [NSMutableString stringWithCapacity:16];
23 |
24 | for (NSUInteger currentByteIndex = 0; currentByteIndex < bytesToConvert; currentByteIndex++) {
25 | switch (currentByteIndex) {
26 | case 3:
27 | case 5:
28 | case 7:
29 | case 9:[outputString appendFormat:@"%02x-", uuidBytes[currentByteIndex]]; break;
30 | default:[outputString appendFormat:@"%02x", uuidBytes[currentByteIndex]];
31 | }
32 | }
33 | return outputString;
34 | }
35 |
36 | @end
37 |
--------------------------------------------------------------------------------
/OKBluetooth/Classes/OKBluetooth.h:
--------------------------------------------------------------------------------
1 | //
2 | // OKBluetooth.h
3 | // OKBluetooth
4 | //
5 | // Created by yuhanle on 2018/7/25.
6 | //
7 |
8 | #import "OKUtils.h"
9 | #import "OKCentralManager.h"
10 | #import "OKPeripheral.h"
11 | #import "OKService.h"
12 | #import "OKCharacteristic.h"
13 | #import
14 |
--------------------------------------------------------------------------------
/OKBluetooth/Classes/OKCentralManager.h:
--------------------------------------------------------------------------------
1 | //
2 | // OKCentralManager.h
3 | // OKBluetooth
4 | //
5 | // Created by yuhanle on 2018/7/25.
6 | //
7 |
8 | #import
9 |
10 | @class RACCommand;
11 | @class RACMulticastConnection;
12 | @class CBCentralManager;
13 |
14 | @interface OKScanModel : NSObject
15 |
16 | /**
17 | * Scan will be stoped after input interaval. default is 30s.
18 | */
19 | @property (nonatomic, assign) NSTimeInterval aScanInterval;
20 |
21 | /**
22 | * An array of CBUUID objects that the app is interested in.
23 | * In this case, each CBUUID object represents the UUID of a service that
24 | * a peripheral is advertising.
25 | */
26 | @property (nonatomic, strong) NSArray *serviceUUIDs;
27 |
28 | /**
29 | * An optional dictionary specifying options to customize the scan.
30 | */
31 | @property (nonatomic, strong) NSDictionary *options;
32 |
33 | /**
34 | * Scans for nearby peripherals with criterias,
35 | * fills the - NSArray *peripherals.
36 | * Scan will be stoped after input interaval
37 |
38 | @param serviceUUIDs An array of CBUUID objects that the app is interested in.
39 | @param options An optional dictionary specifying options to customize the scan.
40 | @param aScanInterval Scan will be stoped after input interaval.
41 | @return A Model with scan options.
42 | */
43 | - (instancetype)initModelWithServiceUUIDs:(NSArray *)serviceUUIDs options:(NSDictionary *)options aScanInterval:(NSTimeInterval)aScanInterval;
44 |
45 | @end
46 |
47 | /**
48 | * Wrapper class whicj implments common central role
49 | * over Core Bluetooth's CBCenteralManager instance
50 | */
51 | @interface OKCentralManager : NSObject
52 |
53 | /**
54 | * Indicates if CBCentralManager is scanning for peripheral
55 | */
56 | @property (nonatomic, getter = isScanning) BOOL scanning;
57 |
58 | /**
59 | * Indicates if central manager is readt for core bluetooth tasks. KVO observable.
60 | */
61 | @property (assign, nonatomic, readonly, getter = isCentralReady) BOOL centralReady;
62 |
63 | /**
64 | * Threshould to stop scanning for peripherals.
65 | * When the number of discovered peripherals exceeds this value, scanning will be
66 | * stopped even before the scan-interval
67 | */
68 | @property (assign, nonatomic) NSUInteger peripheralsCountToStop;
69 |
70 | /**
71 | * Human readable property that indicates why central manager is not ready, KVO observable.
72 | */
73 | @property (weak, nonatomic, readonly) NSString *centralNotReadyReason;
74 |
75 | /**
76 | * Peripherals that are nearby (sorted descending by RSSI values)
77 | */
78 | @property (weak, nonatomic, readonly) NSArray *peripherals;
79 |
80 | /**
81 | * Core bluetooth's Central manager, for implementing central role
82 | */
83 | @property (strong, nonatomic, readonly) CBCentralManager *manager;
84 |
85 | /**
86 | * CBCentralManager's state signal updated by centralManagerDidUpdateState:
87 | */
88 | @property (strong, nonatomic, readonly) RACMulticastConnection *centralManagerStateConnection;
89 |
90 | /**
91 | * Scans for nearby peripherals
92 | * and fills the - NSArray *peripherals
93 | * input @see OKScanModel
94 | */
95 | @property (strong, nonatomic, readonly) RACCommand *scanForPeripheralsCommand;
96 |
97 | /**
98 | * CBCentralManager's connection multicastConnection.
99 | */
100 | @property (strong, nonatomic, readonly) RACMulticastConnection *connectPeripheralConnection;
101 |
102 | /**
103 | * Stops ongoing scan proccess
104 | */
105 | - (void)stopScanForPeripherals;
106 |
107 | /**
108 | * Returns a list of known peripherals by their identifiers.
109 | * @param identifiers A list of peripheral identifiers (represented by NSUUID objects)
110 | * from which LGperipheral objects can be retrieved.
111 | * @return A list of peripherals that the central manager is able to match to the provided identifiers.
112 | */
113 | - (NSArray *)retrievePeripheralsWithIdentifiers:(NSArray *)identifiers;
114 |
115 | /**
116 | * Returns a list of the peripherals (containing any of the specified services) currently connected to the system.
117 | * The list of connected peripherals can include those that are connected by other apps
118 | * and that will need to be connected locally using the connectPeripheral:options: method before they can be used.
119 | * @param serviceUUIDs A list of service UUIDs (represented by CBUUID objects).
120 | * @return A list of the LGPeripherals that are currently connected to
121 | * the system and that contain any of the services specified in the serviceUUID parameter.
122 | */
123 | - (NSArray *)retrieveConnectedPeripheralsWithServices:(NSArray *)serviceUUIDs;
124 |
125 | /**
126 | @return Singleton instance of Central manager
127 | */
128 | + (OKCentralManager *)sharedInstance;
129 |
130 | @end
131 |
--------------------------------------------------------------------------------
/OKBluetooth/Classes/OKCentralManager.m:
--------------------------------------------------------------------------------
1 | //
2 | // OKCentralManager.m
3 | // OKBluetooth
4 | //
5 | // Created by yuhanle on 2018/7/25.
6 | //
7 |
8 | #import "OKCentralManager.h"
9 |
10 | #if TARGET_OS_IPHONE
11 | #import
12 | #elif TARGET_OS_MAC
13 | #import
14 | #endif
15 | #import "OKPeripheral.h"
16 | #import "OKUtils.h"
17 | #import
18 |
19 | @implementation OKScanModel
20 |
21 | - (instancetype)init {
22 | if (self = [super init]) {
23 | self.options = @{CBCentralManagerScanOptionAllowDuplicatesKey : @YES};
24 | self.aScanInterval = 30;
25 | }
26 | return self;
27 | }
28 |
29 | - (instancetype)initModelWithServiceUUIDs:(NSArray *)serviceUUIDs options:(NSDictionary *)options aScanInterval:(NSTimeInterval)aScanInterval {
30 | if (self = [super init]) {
31 | self.options = options ? options : @{CBCentralManagerScanOptionAllowDuplicatesKey : @NO};
32 | self.aScanInterval = aScanInterval ? aScanInterval : 30;
33 | self.serviceUUIDs = serviceUUIDs;
34 | }
35 | return self;
36 | }
37 |
38 | @end
39 |
40 | @interface OKCentralManager ()
41 |
42 | /**
43 | * Ongoing operations
44 | */
45 | @property (strong, atomic) NSMutableDictionary *operations;
46 |
47 | /**
48 | * CBCentralManager's dispatch queue
49 | */
50 | @property (strong, nonatomic) dispatch_queue_t centralQueue;
51 |
52 | /**
53 | * List of scanned peripherals
54 | */
55 | @property (strong, nonatomic) NSMutableArray *scannedPeripherals;
56 | /**
57 | * CBCentralManager's state updated by centralManagerDidUpdateState:
58 | */
59 | @property(nonatomic, assign) CBCentralManagerState cbCentralManagerState;
60 |
61 | /**
62 | * CBCentralManager scanService's subscriber
63 | */
64 | @property (nonatomic, strong) id scanServiceSubscriber;
65 |
66 | /**
67 | * CBCentralManager connectService's subscriber
68 | */
69 | @property (nonatomic, strong) id connectServiceSubscriber;
70 |
71 | @end
72 |
73 | @implementation OKCentralManager
74 |
75 | /*----------------------------------------------------*/
76 | #pragma mark - Getter/Setter -
77 | /*----------------------------------------------------*/
78 |
79 | - (BOOL)isCentralReady
80 | {
81 | return (self.manager.state == CBCentralManagerStatePoweredOn);
82 | }
83 |
84 | - (NSString *)centralNotReadyReason
85 | {
86 | return [self stateMessage];
87 | }
88 |
89 | - (NSArray *)peripherals
90 | {
91 | // Sorting OKPeripherals by RSSI values
92 | NSArray *sortedArray;
93 | sortedArray = [_scannedPeripherals sortedArrayUsingComparator:^NSComparisonResult(OKPeripheral *a, OKPeripheral *b) {
94 | return labs(a.RSSI) > labs(b.RSSI);
95 | }];
96 | return sortedArray;
97 | }
98 |
99 | #pragma mark - CBCentralManagerDelegate
100 | - (void)centralManagerDidUpdateState:(nonnull CBCentralManager *)central
101 | {
102 | self.cbCentralManagerState = (CBCentralManagerState)central.state;
103 | NSString *message = [self stateMessage];
104 | if (message) {
105 | dispatch_async(dispatch_get_main_queue(), ^{
106 | OKLogError(@"%@", message);
107 | });
108 | }
109 | }
110 |
111 | - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
112 | OKPeripheral *okPeripheral = [self wrapperByPeripheral:peripheral];
113 | if (!okPeripheral.RSSI) {
114 | okPeripheral.RSSI = [RSSI integerValue];
115 | } else {
116 | // Calculating AVG RSSI
117 | okPeripheral.RSSI = okPeripheral.RSSI * [RSSI integerValue] / 2;
118 | }
119 | okPeripheral.advertisingData = advertisementData;
120 |
121 | [self.scanServiceSubscriber sendNext:okPeripheral];
122 | if (self.scannedPeripherals.count >= self.peripheralsCountToStop) {
123 | [self.scanServiceSubscriber sendCompleted];
124 | [self stopScanForPeripherals];
125 | }
126 | }
127 |
128 | - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
129 | {
130 | OKPeripheral *okPeripheral = [self wrapperByPeripheral:peripheral];
131 | [okPeripheral handleConnectionWithError:nil];
132 |
133 | [self.connectServiceSubscriber sendNext:okPeripheral];
134 | }
135 |
136 | - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
137 | {
138 | OKPeripheral *okPeripheral = [self wrapperByPeripheral:peripheral];
139 | [okPeripheral handleConnectionWithError:error];
140 |
141 | [self.connectServiceSubscriber sendNext:okPeripheral];
142 | }
143 |
144 | - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
145 | {
146 | OKPeripheral *okPeripheral = [self wrapperByPeripheral:peripheral];
147 | [okPeripheral handleDisconnectWithError:error];
148 |
149 | [self.connectServiceSubscriber sendNext:okPeripheral];
150 | //[self.scannedPeripherals removeObject:okPeripheral];
151 | }
152 |
153 | - (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary *)dict {
154 | NSArray *peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey];
155 | [[self wrappersByPeripherals:peripherals] enumerateObjectsUsingBlock:^(OKPeripheral *obj, NSUInteger idx, BOOL * _Nonnull stop) {
156 | [obj.manager.manager connectPeripheral:obj.cbPeripheral options:@{CBConnectPeripheralOptionNotifyOnConnectionKey:@YES,
157 | CBConnectPeripheralOptionNotifyOnDisconnectionKey:@YES,
158 | CBConnectPeripheralOptionNotifyOnNotificationKey:@YES}];
159 | [self.connectServiceSubscriber sendNext:obj];
160 | }];
161 | }
162 |
163 | /*----------------------------------------------------*/
164 | #pragma mark - Public Methods -
165 | /*----------------------------------------------------*/
166 | - (void)stopScanForPeripherals
167 | {
168 | self.scanning = NO;
169 | [self.manager stopScan];
170 | }
171 |
172 | - (NSArray *)retrievePeripheralsWithIdentifiers:(NSArray *)identifiers
173 | {
174 | return [self wrappersByPeripherals:[self.manager retrievePeripheralsWithIdentifiers:identifiers]];
175 | }
176 |
177 | - (NSArray *)retrieveConnectedPeripheralsWithServices:(NSArray *)serviceUUIDs
178 | {
179 | return [self wrappersByPeripherals:[self.manager retrieveConnectedPeripheralsWithServices:serviceUUIDs]];
180 | }
181 |
182 | /*----------------------------------------------------*/
183 | #pragma mark - Private Methods -
184 | /*----------------------------------------------------*/
185 |
186 | - (NSString *)stateMessage
187 | {
188 | NSString *message = nil;
189 | switch (self.manager.state) {
190 | case CBCentralManagerStateUnsupported:
191 | message = @"The platform/hardware doesn't support Bluetooth Low Energy.";
192 | break;
193 | case CBCentralManagerStateUnauthorized:
194 | message = @"The app is not authorized to use Bluetooth Low Energy.";
195 | break;
196 | case CBCentralManagerStateUnknown:
197 | message = @"Central not initialized yet.";
198 | break;
199 | case CBCentralManagerStatePoweredOff:
200 | message = @"Bluetooth is currently powered off.";
201 | break;
202 | case CBCentralManagerStatePoweredOn:
203 | message = @"";
204 | break;
205 | default:
206 | message = @"";
207 | break;
208 | }
209 | return message;
210 | }
211 |
212 | - (OKPeripheral *)wrapperByPeripheral:(CBPeripheral *)aPeripheral
213 | {
214 | OKPeripheral *wrapper = nil;
215 | for (OKPeripheral *scanned in self.scannedPeripherals) {
216 | if (scanned.cbPeripheral == aPeripheral) {
217 | wrapper = scanned;
218 | break;
219 | }
220 | }
221 | if (!wrapper) {
222 | wrapper = [[OKPeripheral alloc] initWithPeripheral:aPeripheral manager:self];
223 | [self.scannedPeripherals addObject:wrapper];
224 | }
225 | return wrapper;
226 | }
227 |
228 | - (NSArray *)wrappersByPeripherals:(NSArray *)peripherals
229 | {
230 | NSMutableArray *okPeripherals = [NSMutableArray new];
231 |
232 | for (CBPeripheral *peripheral in peripherals) {
233 | [okPeripherals addObject:[self wrapperByPeripheral:peripheral]];
234 | }
235 | return okPeripherals;
236 | }
237 |
238 | #pragma mark - InitializeRAC -
239 | - (void)initializeRAC
240 | {
241 | @weakify(self);
242 | RACSignal *centralManagerStateSingal = [RACObserve(self, cbCentralManagerState) doNext:^(NSNumber *x) {
243 | @strongify(self);
244 | if (x.integerValue != CBCentralManagerStatePoweredOn) {
245 | if (self.scanServiceSubscriber) {
246 | [self.scanServiceSubscriber sendError:[OKUtils scanErrorWithCode:self.cbCentralManagerState message:[self stateMessage]]];
247 | self.scanServiceSubscriber = nil;
248 | }
249 | }
250 | }];
251 | RACSignal *connectPeripheralSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
252 | @strongify(self);
253 | self.connectServiceSubscriber = subscriber;
254 | return [RACDisposable disposableWithBlock:^{
255 |
256 | }];
257 | }];
258 |
259 | _centralManagerStateConnection = [centralManagerStateSingal publish];
260 | _connectPeripheralConnection = [connectPeripheralSignal publish];
261 | [_centralManagerStateConnection connect];
262 | [_connectPeripheralConnection connect];
263 |
264 | _scanForPeripheralsCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(OKScanModel * _Nonnull input) {
265 | RACSubject *timoutSubject = [RACSubject subject];
266 | RACSignal *scanSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
267 | @strongify(self);
268 | if (self.cbCentralManagerState != CBCentralManagerStatePoweredOn) {
269 | [timoutSubject sendCompleted];
270 | [subscriber sendError:[OKUtils scanErrorWithCode:self.cbCentralManagerState message:[self stateMessage]]];
271 | return nil;
272 | }
273 |
274 | [self.scannedPeripherals removeAllObjects];
275 | [self.manager scanForPeripheralsWithServices:input.serviceUUIDs options:input.options];
276 | self.scanServiceSubscriber = subscriber;
277 | return [RACDisposable disposableWithBlock:^{
278 | [timoutSubject sendCompleted];
279 | [self stopScanForPeripherals];
280 | }];
281 | }];
282 |
283 | // Subscribe command errors, then we can know the timeout
284 | [[timoutSubject timeout:input.aScanInterval onScheduler:[RACScheduler mainThreadScheduler]] subscribeError:^(NSError * _Nullable error) {
285 | if (error.code == 1) {
286 | if (self.scanServiceSubscriber) {
287 | [self.scanServiceSubscriber sendError:[OKUtils scanErrorWithCode:kOKUtilsScanTimeoutErrorCode message:kOKUtilsScanTimeoutErrorMessage]];
288 | self.scanServiceSubscriber = nil;
289 | }
290 | }
291 | }];
292 |
293 | return [scanSignal throttle:0.0];
294 | }];
295 |
296 | RAC(self, scanning) = [self.scanForPeripheralsCommand executing];
297 | }
298 |
299 | /*----------------------------------------------------*/
300 | #pragma mark - LifeCycle -
301 | /*----------------------------------------------------*/
302 |
303 | static OKCentralManager *sharedInstance = nil;
304 |
305 | + (OKCentralManager *)sharedInstance
306 | {
307 | // Thread blocking to be sure for singleton instance
308 | @synchronized(self) {
309 | if (!sharedInstance) {
310 | sharedInstance = [OKCentralManager new];
311 | [sharedInstance initializeRAC];
312 | }
313 | }
314 | return sharedInstance;
315 | }
316 |
317 | - (id)init
318 | {
319 | self = [super init];
320 | if (self) {
321 | _centralQueue = dispatch_queue_create("com.OKBluetooth.OKCentralQueue", DISPATCH_QUEUE_SERIAL);
322 | _manager = [[CBCentralManager alloc] initWithDelegate:self queue:self.centralQueue options:@{ CBCentralManagerOptionRestoreIdentifierKey: @"com.OKBluetooth.restoreIdentifier" }];
323 | _cbCentralManagerState = (CBCentralManagerState)_manager.state;
324 | _scannedPeripherals = [NSMutableArray new];
325 | _peripheralsCountToStop = NSUIntegerMax;
326 | }
327 | return self;
328 | }
329 |
330 | @end
331 |
--------------------------------------------------------------------------------
/OKBluetooth/Classes/OKCharacteristic.h:
--------------------------------------------------------------------------------
1 | //
2 | // OKCharacteristic.h
3 | // OKBluetooth
4 | //
5 | // Created by yuhanle on 2018/7/25.
6 | //
7 |
8 | #import
9 | #import
10 |
11 | @class CBCharacteristic;
12 | @class RACCommand;
13 | @class RACSignal;
14 | @protocol RACSubscriber;
15 |
16 | @interface OKWriteValueModel : NSObject
17 |
18 | /**
19 | * Write time. Default is 30s.
20 | */
21 | @property (nonatomic, assign) NSTimeInterval aInterval;
22 |
23 | /**
24 | * The Data will be send.
25 | */
26 | @property (nonatomic, strong) NSData *data;
27 |
28 | /**
29 | * Write type. Default is @see CBCharacteristicWriteWithoutResponse.
30 | */
31 | @property (nonatomic, assign) CBCharacteristicWriteType type;
32 |
33 | /**
34 | * Written data length
35 | */
36 | @property (nonatomic, assign) NSInteger offset;
37 |
38 | /**
39 | * Identifier more data to write.
40 | */
41 | @property (nonatomic, assign, getter = hasMore, readonly) BOOL more;
42 |
43 | /**
44 | * Sub data
45 |
46 | @return NSData
47 | */
48 | - (NSData *)subData;
49 |
50 | @end
51 |
52 | @interface OKCharacteristic : NSObject
53 |
54 | /**
55 | * Core Bluetooth's CBCharacteristic instance
56 | */
57 | @property (strong, nonatomic, readonly) CBCharacteristic *cbCharacteristic;
58 |
59 | /**
60 | * NSString representation of 16/128 bit CBUUID
61 | */
62 | @property (strong, nonatomic, readonly) NSString *UUIDString;
63 |
64 | /**
65 | * Enables or disables notifications/indications for the characteristic
66 | * value of characteristic.
67 | * input Enable/Disable notifications
68 | * SubscriberNext will return the latest value
69 | */
70 | @property (nonatomic, strong, readonly) RACCommand *notifyValueCommand;
71 |
72 | /**
73 | * Writes input data to characteristic
74 | * input @see OKWriteValueModel object representing bytes that needs to be written
75 | */
76 | @property (nonatomic, strong, readonly) RACCommand *writeValueCommand;
77 |
78 | /**
79 | * Reads characteristic value
80 | * input null
81 | */
82 | @property (nonatomic, strong, readonly) RACCommand *readValueCommand;
83 |
84 | /**
85 | * NotifyValue update
86 | */
87 | @property (nonatomic, strong) RACSignal *notifyValueSignal;
88 |
89 | // ----- Used for input events -----/
90 |
91 | - (void)handleSetNotifiedWithError:(NSError *)anError;
92 |
93 | - (void)handleReadValue:(NSData *)aValue error:(NSError *)anError;
94 |
95 | - (void)handleWrittenValueWithError:(NSError *)anError;
96 |
97 | /**
98 | * @return Wrapper object over Core Bluetooth's CBCharacteristic
99 | */
100 | - (instancetype)initWithCharacteristic:(CBCharacteristic *)aCharacteristic;
101 |
102 | @end
103 |
--------------------------------------------------------------------------------
/OKBluetooth/Classes/OKCharacteristic.m:
--------------------------------------------------------------------------------
1 | //
2 | // OKCharacteristic.m
3 | // OKBluetooth
4 | //
5 | // Created by yuhanle on 2018/7/25.
6 | //
7 |
8 | #import "OKCharacteristic.h"
9 |
10 | #import "CBUUID+StringExtraction.h"
11 | #if TARGET_OS_IPHONE
12 | #import
13 | #elif TARGET_OS_MAC
14 | #import
15 | #endif
16 | #import
17 | #import "OKUtils.h"
18 |
19 | @implementation OKWriteValueModel
20 |
21 | - (instancetype)init {
22 | if (self = [super init]) {
23 | self.aInterval = 30.0;
24 | self.type = CBCharacteristicWriteWithoutResponse;
25 | }
26 | return self;
27 | }
28 |
29 | - (BOOL)hasMore {
30 | if (self.offset < self.data.length) {
31 | return YES;
32 | }
33 | return NO;
34 | }
35 |
36 | - (NSData *)subData {
37 | NSInteger totalLength = self.data.length;
38 | NSInteger remainLength = totalLength - self.offset;
39 | NSInteger rangLength = remainLength > 20 ? 20 : remainLength;
40 | return [self.data subdataWithRange:NSMakeRange(self.offset, rangLength)];
41 | }
42 |
43 | @end
44 |
45 | @interface OKCharacteristic ()
46 |
47 | /**
48 | * When write a long data, use.
49 | */
50 | @property (nonatomic, strong) OKWriteValueModel *writeData;
51 |
52 | /**
53 | * Peripheral's notifyValue subscriber
54 | */
55 | @property (nonatomic, strong) id notifyValueSubscriber;
56 |
57 | /**
58 | * Peripheral's notifyValue subscriber
59 | */
60 | @property (nonatomic, strong) id notifySubscriber;
61 |
62 | /**
63 | * Peripheral's writeValue subscriber
64 | */
65 | @property (nonatomic, strong) id writeValueSubscriber;
66 |
67 | /**
68 | * Peripheral's readValue subscriber
69 | */
70 | @property (nonatomic, strong) id readValueSubscriber;
71 |
72 | /**
73 | * Writes input data to characteristic
74 | * input @see OKWriteValueModel object representing bytes that needs to be written
75 | */
76 | @property (nonatomic, strong, readonly) RACCommand *writeDataCommand;
77 |
78 | /**
79 | * Peripheral's writeData subscriber
80 | */
81 | @property (nonatomic, strong) id writeDataSubscriber;
82 |
83 | @end
84 |
85 | @implementation OKCharacteristic
86 |
87 | /*----------------------------------------------------*/
88 | #pragma mark - Getter/Setter -
89 | /*----------------------------------------------------*/
90 |
91 | - (NSString *)UUIDString
92 | {
93 | return [self.cbCharacteristic.UUID representativeString];
94 | }
95 |
96 | - (NSString *)description
97 | {
98 | NSString *org = [super description];
99 |
100 | return [org stringByAppendingFormat:@" UUIDString: %@", self.UUIDString];
101 | }
102 |
103 | /*----------------------------------------------------*/
104 | #pragma mark - Private Methods -
105 | /*----------------------------------------------------*/
106 |
107 | - (void)loopWirteValue {
108 | [[self.writeDataCommand execute:self.writeData] subscribeNext:^(id _Nullable x) {
109 | [self.writeValueSubscriber sendNext:x];
110 | } error:^(NSError * _Nullable error) {
111 | [self.writeValueSubscriber sendError:error];
112 | } completed:^{
113 | [self.writeValueSubscriber sendCompleted];
114 | }];
115 | }
116 |
117 | /*----------------------------------------------------*/
118 | #pragma mark - Handler Methods -
119 | /*----------------------------------------------------*/
120 |
121 | - (void)handleSetNotifiedWithError:(NSError *)anError
122 | {
123 | OKLog(@"Characteristic - %@ notify changed with error - %@", self.cbCharacteristic.UUID, anError);
124 |
125 | if (anError) {
126 | [self.notifyValueSubscriber sendError:anError];
127 | }else {
128 | [self.notifyValueSubscriber sendNext:anError];
129 | [self.notifyValueSubscriber sendCompleted];
130 | }
131 | }
132 |
133 | - (void)handleReadValue:(NSData *)aValue error:(NSError *)anError
134 | {
135 | OKLog(@"Characteristic - %@ value - %s error - %@", self.cbCharacteristic.UUID, [aValue bytes], anError);
136 |
137 | if (anError) {
138 | [self.readValueSubscriber sendError:anError];
139 | }else {
140 | [self.notifySubscriber sendNext:aValue];
141 | [self.readValueSubscriber sendNext:aValue];
142 | [self.readValueSubscriber sendCompleted];
143 | }
144 | }
145 |
146 | - (void)handleWrittenValueWithError:(NSError *)anError
147 | {
148 | OKLog(@"Characteristic - %@ wrote with error - %@", self.cbCharacteristic.UUID, anError);
149 |
150 | if (anError) {
151 | [self.writeDataSubscriber sendError:anError];
152 | }else {
153 | if ([self.writeData hasMore]) {
154 | [self.writeDataSubscriber sendNext:self.writeData];
155 |
156 | // Continue send
157 | [self loopWirteValue];
158 | }else {
159 | [self.writeDataSubscriber sendNext:self.writeData];
160 | [self.writeDataSubscriber sendCompleted];
161 | }
162 | }
163 | }
164 |
165 | #pragma mark - InitializeRAC -
166 |
167 | - (void)initializeRAC {
168 | @weakify(self);
169 | _notifyValueCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(NSNumber *input) {
170 | @strongify(self);
171 | return [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
172 | if (self.cbCharacteristic.service.peripheral.state != CBPeripheralStateConnected) {
173 | [subscriber sendError:[OKUtils readErrorWithCode:kOKUtilsMissingCharacteristicErrorCode message:kOKUtilsMissingCharacteristicErrorMessage]];
174 | return nil;
175 | }
176 | self.notifyValueSubscriber = subscriber;
177 | [self.cbCharacteristic.service.peripheral setNotifyValue:input.boolValue forCharacteristic:self.cbCharacteristic];
178 | return [RACDisposable disposableWithBlock:^{
179 | self.notifyValueSubscriber = nil;
180 | }];
181 | }];
182 | }];
183 |
184 | _writeDataCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(OKWriteValueModel *input) {
185 | @strongify(self);
186 | if (input.data.length == 0) {
187 | return [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
188 | [subscriber sendError:[OKUtils writeErrorWithCode:kOKUtilsMissingCharacteristicDataErrorCode message:kOKUtilsMissingCharacteristicDataErrorMessage]];
189 | return [RACDisposable disposableWithBlock:^{
190 |
191 | }];
192 | }];
193 | }
194 |
195 | if (input.type == CBCharacteristicWriteWithoutResponse) {
196 | return [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
197 | if (self.cbCharacteristic.service.peripheral.state != CBPeripheralStateConnected) {
198 | [subscriber sendError:[OKUtils writeErrorWithCode:kOKUtilsMissingCharacteristicErrorCode message:kOKUtilsMissingCharacteristicErrorMessage]];
199 | return nil;
200 | }
201 | [self.cbCharacteristic.service.peripheral writeValue:input.subData forCharacteristic:self.cbCharacteristic type:input.type];
202 | [subscriber sendCompleted];
203 | return [RACDisposable disposableWithBlock:^{
204 |
205 | }];
206 | }];
207 | }
208 |
209 | RACSubject *timeoutSubject = [RACSubject subject];
210 | RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
211 | self.writeDataSubscriber = subscriber;
212 | [self.cbCharacteristic.service.peripheral writeValue:input.subData forCharacteristic:self.cbCharacteristic type:input.type];
213 | return [RACDisposable disposableWithBlock:^{
214 | [timeoutSubject sendCompleted];
215 | self.writeDataSubscriber = nil;
216 | }];
217 | }];
218 |
219 | // Subscribe command errors, then we can know the timeout
220 | [[timeoutSubject timeout:input.aInterval onScheduler:[RACScheduler mainThreadScheduler]] subscribeError:^(NSError * _Nullable error) {
221 | if (error.code == 1) {
222 | [self.writeDataSubscriber sendError:[OKUtils scanErrorWithCode:kOKUtilsMissingCharacteristicErrorCode message:kOKUtilsMissingCharacteristicErrorMessage]];
223 | }
224 | }];
225 |
226 | return signal;
227 | }];
228 |
229 | _writeValueCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(OKWriteValueModel *input) {
230 | @strongify(self);
231 | return [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
232 | self.writeValueSubscriber = subscriber;
233 | self.writeData = input;
234 |
235 | [self loopWirteValue];
236 | return [RACDisposable disposableWithBlock:^{
237 | self.writeValueSubscriber = nil;
238 | }];
239 | }];
240 | }];
241 |
242 | _readValueCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
243 | @strongify(self);
244 | return [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
245 | if (self.cbCharacteristic.service.peripheral.state != CBPeripheralStateConnected) {
246 | [subscriber sendError:[OKUtils readErrorWithCode:kOKUtilsMissingCharacteristicErrorCode message:kOKUtilsMissingCharacteristicErrorMessage]];
247 | return nil;
248 | }
249 | self.readValueSubscriber = subscriber;
250 | [self.cbCharacteristic.service.peripheral readValueForCharacteristic:self.cbCharacteristic];
251 | return [RACDisposable disposableWithBlock:^{
252 | self.readValueSubscriber = nil;
253 | }];
254 | }];
255 | }];
256 |
257 | _notifyValueSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
258 | @strongify(self);
259 | self.notifySubscriber = subscriber;
260 | return [RACDisposable disposableWithBlock:^{
261 | self.notifyValueSignal = nil;
262 | }];
263 | }];
264 | }
265 |
266 | /*----------------------------------------------------*/
267 | #pragma mark - Lifecycle -
268 | /*----------------------------------------------------*/
269 |
270 | - (instancetype)initWithCharacteristic:(CBCharacteristic *)aCharacteristic
271 | {
272 | if (self = [super init]) {
273 | _cbCharacteristic = aCharacteristic;
274 | [self initializeRAC];
275 | }
276 | return self;
277 | }
278 |
279 | @end
280 |
--------------------------------------------------------------------------------
/OKBluetooth/Classes/OKPeripheral.h:
--------------------------------------------------------------------------------
1 | //
2 | // OKPeripheral.h
3 | // OKBluetooth
4 | //
5 | // Created by yuhanle on 2018/7/25.
6 | //
7 |
8 | #import
9 |
10 | @class CBPeripheral;
11 | @class OKCentralManager;
12 | @class RACCommand;
13 | @class RACSignal;
14 | @class RACMulticastConnection;
15 | @protocol RACSubscriber;
16 |
17 | #pragma mark - Error Domains -
18 |
19 | /**
20 | * Error domains for Connection errors
21 | */
22 | extern NSString * const kOKPeripheralConnectionErrorDomain;
23 |
24 | #pragma mark - Error Codes -
25 | /**
26 | * Connection timeout error code
27 | */
28 | extern const NSInteger kConnectionTimeoutErrorCode;
29 |
30 | /**
31 | * Connection missing error code
32 | */
33 | extern const NSInteger kConnectionMissingErrorCode;
34 |
35 | #pragma mark - Error Messages -
36 |
37 | /**
38 | * Error message for connection timeouts
39 | */
40 | extern NSString * const kConnectionTimeoutErrorMessage;
41 |
42 | /**
43 | * Error message for missing connections
44 | */
45 | extern NSString * const kConnectionMissingErrorMessage;
46 |
47 | @interface OKPeripheral : NSObject
48 |
49 | /**
50 | * Core Bluetooth's CBPeripheral instance
51 | */
52 | @property (strong, nonatomic, readonly) CBPeripheral *cbPeripheral;
53 |
54 | /**
55 | * OKCentralManager's instance used to connect to peripherals
56 | */
57 | @property (unsafe_unretained, nonatomic, readonly) OKCentralManager *manager;
58 |
59 | /**
60 | * Connection state of peripheral
61 | */
62 | @property (readonly, nonatomic, getter = isConnected) BOOL connected;
63 |
64 | /**
65 | * Flag to indicate discovering services or not
66 | */
67 | @property (assign, nonatomic, readonly, getter = isDiscoveringServices) BOOL discoveringServices;
68 |
69 | /**
70 | * Available services for this service,
71 | * will be updated after calling discoverServicesWithCompletion:
72 | */
73 | @property (strong, nonatomic, readonly) NSArray *services;
74 |
75 | /**
76 | * UUID Identifier of peripheral
77 | */
78 | @property (weak, nonatomic, readonly) NSString *UUIDString;
79 |
80 | /**
81 | * Name of peripheral
82 | */
83 | @property (weak, nonatomic, readonly) NSString *name;
84 |
85 | /**
86 | * Sinal strength of peripheral
87 | */
88 | @property (assign, nonatomic) NSInteger RSSI;
89 |
90 | /**
91 | * The advertisement data that was tracked from peripheral
92 | */
93 | @property (strong, nonatomic) NSDictionary *advertisingData;
94 |
95 | /**
96 | * The error data when connect/disconnect. Default is nil
97 | */
98 | @property (strong, nonatomic, readonly) NSError *error;
99 |
100 | /**
101 | * Opens connection to this peripheral
102 | * input aWatchDogInterval timeout after which, connection will be closed (if it was in stage isConnecting)
103 | */
104 | @property (nonatomic, strong, readonly) RACCommand *connectCommand;
105 |
106 | /**
107 | * Disconnects from peripheral peripheral
108 | */
109 | @property (nonatomic, strong, readonly) RACCommand *disConnectCommand;
110 |
111 | /**
112 | * Discoveres All services of this peripheral
113 | * input serviceUUIDs Array of CBUUID's that contain service UUIDs which we need to discover
114 | */
115 | @property (nonatomic, strong, readonly) RACCommand *discoverServicesCommand;
116 |
117 | /**
118 | * Reads current RSSI of this peripheral, (note : requires active connection to peripheral)
119 | */
120 | @property (nonatomic, strong, readonly) RACCommand *readRSSIValueCommand;
121 |
122 | /**
123 | * Current RSSI of this peripheral, (note : requires active connection to peripheral)
124 | */
125 | @property (nonatomic, strong, readonly) RACSignal *rssiSignal;
126 |
127 | #pragma mark - Private Handlers -
128 |
129 | // ----- Used for input events -----/
130 |
131 | - (void)handleConnectionWithError:(NSError *)anError;
132 |
133 | - (void)handleDisconnectWithError:(NSError *)anError;
134 |
135 | #pragma mark - Private Initializer -
136 | /**
137 | * @return Wrapper object over Core Bluetooth's CBPeripheral
138 | */
139 | - (instancetype)initWithPeripheral:(CBPeripheral *)aPeripheral manager:(OKCentralManager *)manager;
140 |
141 | @end
142 |
--------------------------------------------------------------------------------
/OKBluetooth/Classes/OKPeripheral.m:
--------------------------------------------------------------------------------
1 | //
2 | // OKPeripheral.m
3 | // OKBluetooth
4 | //
5 | // Created by yuhanle on 2018/7/25.
6 | //
7 |
8 | #if TARGET_OS_IPHONE
9 | #import
10 | #elif TARGET_OS_MAC
11 | #import
12 | #endif
13 |
14 | #import "OKPeripheral.h"
15 | #import
16 | #import "OKCentralManager.h"
17 | #import "OKService.h"
18 | #import "OKCharacteristic.h"
19 | #import "OKUtils.h"
20 |
21 | // Error Domains
22 | NSString * const kOKPeripheralConnectionErrorDomain = @"OKPeripheralConnectionErrorDomain";
23 |
24 | // Error Codes
25 | const NSInteger kConnectionTimeoutErrorCode = 408;
26 | const NSInteger kConnectionMissingErrorCode = 409;
27 |
28 | NSString * const kConnectionTimeoutErrorMessage = @"BLE Device can't be connected by given interval";
29 | NSString * const kConnectionMissingErrorMessage = @"BLE Device is not connected";
30 |
31 | @interface OKPeripheral ()
32 |
33 | /**
34 | * Peripheral's connect subscriber
35 | */
36 | @property (nonatomic, strong) id connectServiceSubscriber;
37 |
38 | /**
39 | * Peripheral's disConnect subscriber
40 | */
41 | @property (nonatomic, strong) id disConnectServiceSubscriber;
42 |
43 | /**
44 | * Peripheral's discoverServices subscriber
45 | */
46 | @property (nonatomic, strong) id discoverServicesSubscriber;
47 |
48 | /**
49 | * Peripheral's rssi subscriber
50 | */
51 | @property (nonatomic, strong) id rssiValueSubscriber;
52 |
53 | @end
54 |
55 | @implementation OKPeripheral
56 |
57 | /*----------------------------------------------------*/
58 | #pragma mark - Getter/Setter -
59 | /*----------------------------------------------------*/
60 |
61 | - (BOOL)isConnected
62 | {
63 | return (self.cbPeripheral.state == CBPeripheralStateConnected);
64 | }
65 |
66 | - (NSString *)UUIDString
67 | {
68 | return [self.cbPeripheral.identifier UUIDString];
69 | }
70 |
71 | - (NSString *)name
72 | {
73 | return [self.cbPeripheral name];
74 | }
75 |
76 | /*----------------------------------------------------*/
77 | #pragma mark - Handler Methods -
78 | /*----------------------------------------------------*/
79 |
80 | - (void)handleConnectionWithError:(NSError *)anError
81 | {
82 | _error = anError;
83 | OKLog(@"Connection with error - %@", anError);
84 |
85 | if (anError) {
86 | [self.connectServiceSubscriber sendError:anError];
87 | }else {
88 | [self.connectServiceSubscriber sendNext:self];
89 | [self.connectServiceSubscriber sendCompleted];
90 | }
91 | }
92 |
93 | - (void)handleDisconnectWithError:(NSError *)anError
94 | {
95 | _error = anError;
96 | OKLog(@"Disconnect with error - %@", anError);
97 |
98 | if (anError) {
99 | [self.disConnectServiceSubscriber sendError:anError];
100 | }else {
101 | [self.disConnectServiceSubscriber sendNext:self];
102 | [self.disConnectServiceSubscriber sendCompleted];
103 | }
104 | }
105 |
106 | /*----------------------------------------------------*/
107 | #pragma mark - Overide Methods -
108 | /*----------------------------------------------------*/
109 |
110 | - (NSString *)description
111 | {
112 | NSString *org = [super description];
113 |
114 | return [org stringByAppendingFormat:@" UUIDString: %@", self.UUIDString];
115 | }
116 |
117 | /*----------------------------------------------------*/
118 | #pragma mark - Error Generators -
119 | /*----------------------------------------------------*/
120 |
121 | - (NSError *)connectionErrorWithCode:(NSInteger)aCode message:(NSString *)aMsg
122 | {
123 | return [NSError errorWithDomain:kOKPeripheralConnectionErrorDomain
124 | code:aCode
125 | userInfo:@{kOKErrorMessageKey : aMsg}];
126 | }
127 |
128 | /*----------------------------------------------------*/
129 | #pragma mark - Private Methods -
130 | /*----------------------------------------------------*/
131 |
132 | - (void)updateServiceWrappers
133 | {
134 | NSMutableArray *updatedServices = [NSMutableArray new];
135 | for (CBService *service in self.cbPeripheral.services) {
136 | [updatedServices addObject:[[OKService alloc] initWithService:service]];
137 | }
138 | _services = updatedServices;
139 | }
140 |
141 | - (OKService *)wrapperByService:(CBService *)aService
142 | {
143 | OKService *wrapper = nil;
144 | for (OKService *discovered in self.services) {
145 | if (discovered.cbService == aService) {
146 | wrapper = discovered;
147 | break;
148 | }
149 | }
150 | return wrapper;
151 | }
152 |
153 | #pragma mark - InitializeRAC -
154 |
155 | - (void)initializeRAC
156 | {
157 | @weakify(self);
158 | _connectCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(NSNumber *input) {
159 | @strongify(self);
160 | RACSubject *timeoutSubject = [RACSubject subject];
161 | RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
162 | self.connectServiceSubscriber = subscriber;
163 | [self.manager.manager connectPeripheral:self.cbPeripheral options:@{CBConnectPeripheralOptionNotifyOnConnectionKey:@YES,
164 | CBConnectPeripheralOptionNotifyOnDisconnectionKey:@YES,
165 | CBConnectPeripheralOptionNotifyOnNotificationKey:@YES}];
166 | return [RACDisposable disposableWithBlock:^{
167 | [timeoutSubject sendCompleted];
168 | self.connectServiceSubscriber = nil;
169 | }];
170 | }];
171 |
172 | // Subscribe command errors, then we can know the timeout
173 | [[timeoutSubject timeout:input.integerValue onScheduler:[RACScheduler mainThreadScheduler]] subscribeError:^(NSError * _Nullable error) {
174 | if (error.code == 1) {
175 | [self.connectServiceSubscriber sendError:[self connectionErrorWithCode:kConnectionTimeoutErrorCode message:kConnectionTimeoutErrorMessage]];
176 | }
177 | }];
178 |
179 | return signal;
180 | }];
181 |
182 | _disConnectCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
183 | @strongify(self);
184 | return [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
185 | self.disConnectServiceSubscriber = subscriber;
186 | [self.manager.manager cancelPeripheralConnection:self.cbPeripheral];
187 | return [RACDisposable disposableWithBlock:^{
188 | self.disConnectServiceSubscriber = nil;
189 | }];
190 | }];
191 | }];
192 |
193 | _discoverServicesCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(NSArray *input) {
194 | @strongify(self);
195 | return [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
196 | if (!self.isConnected) {
197 | [subscriber sendError:[self connectionErrorWithCode:kConnectionMissingErrorCode message:kConnectionMissingErrorMessage]];
198 | return nil;
199 | }
200 |
201 | self.discoverServicesSubscriber = subscriber;
202 | [self.cbPeripheral discoverServices:input];
203 | return [RACDisposable disposableWithBlock:^{
204 | self.discoverServicesSubscriber = nil;
205 | }];
206 | }];
207 | }];
208 |
209 | _readRSSIValueCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
210 | @strongify(self);
211 | return [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
212 | self.rssiValueSubscriber = subscriber;
213 | [self.cbPeripheral readRSSI];
214 | return [RACDisposable disposableWithBlock:^{
215 | self.rssiValueSubscriber = nil;
216 | }];
217 | }];
218 | }];
219 |
220 | _rssiSignal = RACObserve(self, RSSI);
221 | RAC(self, discoveringServices) = [self.discoverServicesCommand executing];
222 | }
223 |
224 | /*----------------------------------------------------*/
225 | #pragma mark - CBPeripheral Delegate -
226 | /*----------------------------------------------------*/
227 |
228 | - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
229 | {
230 | _discoveringServices = NO;
231 | [self updateServiceWrappers];
232 |
233 | #if OK_ENABLE_BLE_LOGGING != 0
234 | for (OKService *aService in self.services) {
235 | OKLog(@"Service discovered - %@", aService.cbService.UUID);
236 | }
237 | #endif
238 |
239 | if (error) {
240 | [self.discoverServicesSubscriber sendError:error];
241 | }else {
242 | [self.discoverServicesSubscriber sendNext:self];
243 | [self.discoverServicesSubscriber sendCompleted];
244 | }
245 | }
246 |
247 | - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service
248 | error:(NSError *)error
249 | {
250 | dispatch_async(dispatch_get_main_queue(), ^{
251 | [[self wrapperByService:service] handleDiscoveredCharacteristics:service.characteristics
252 | error:error];
253 | });
254 | }
255 |
256 | - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
257 | error:(NSError *)error
258 | {
259 | NSData *value = [characteristic.value copy];
260 | dispatch_async(dispatch_get_main_queue(), ^{
261 | [[[self wrapperByService:characteristic.service]
262 | wrapperByCharacteristic:characteristic]
263 | handleReadValue:value error:error];
264 | });
265 | }
266 |
267 | - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic
268 | error:(NSError *)error
269 | {
270 | dispatch_async(dispatch_get_main_queue(), ^{
271 | [[[self wrapperByService:characteristic.service]
272 | wrapperByCharacteristic:characteristic]
273 | handleSetNotifiedWithError:error];
274 | });
275 | }
276 |
277 | - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic
278 | error:(NSError *)error
279 | {
280 | dispatch_async(dispatch_get_main_queue(), ^{
281 | [[[self wrapperByService:characteristic.service]
282 | wrapperByCharacteristic:characteristic]
283 | handleWrittenValueWithError:error];
284 | });
285 | }
286 |
287 | - (void)peripheralDidUpdateRSSI:(CBPeripheral *)peripheral error:(NSError *)error
288 | {
289 | if (error) {
290 | [self.rssiValueSubscriber sendError:error];
291 | }else {
292 | self.RSSI = [peripheral.RSSI integerValue];
293 | [self.rssiValueSubscriber sendNext:peripheral.RSSI];
294 | [self.rssiValueSubscriber sendCompleted];
295 | }
296 | }
297 |
298 | - (void)peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(NSError *)error
299 | {
300 | if (error) {
301 | [self.rssiValueSubscriber sendError:error];
302 | }else {
303 | self.RSSI = [RSSI integerValue];
304 | [self.rssiValueSubscriber sendNext:RSSI];
305 | [self.rssiValueSubscriber sendCompleted];
306 | }
307 | }
308 |
309 | /*----------------------------------------------------*/
310 | #pragma mark - Lifecycle -
311 | /*----------------------------------------------------*/
312 |
313 | - (instancetype)initWithPeripheral:(CBPeripheral *)aPeripheral manager:(OKCentralManager *)manager
314 | {
315 | if (self = [super init]) {
316 | _cbPeripheral = aPeripheral;
317 | _cbPeripheral.delegate = self;
318 | _manager = manager;
319 | [self initializeRAC];
320 | }
321 | return self;
322 | }
323 |
324 | @end
325 |
--------------------------------------------------------------------------------
/OKBluetooth/Classes/OKService.h:
--------------------------------------------------------------------------------
1 | //
2 | // OKService.h
3 | // OKBluetooth
4 | //
5 | // Created by yuhanle on 2018/7/25.
6 | //
7 |
8 | #import
9 |
10 | @class CBCharacteristic;
11 | @class CBService;
12 | @class CBPeripheral;
13 | @class OKCharacteristic;
14 | @class RACCommand;
15 | @class RACSignal;
16 | @protocol RACSubscriber;
17 |
18 | @interface OKService : NSObject
19 |
20 | /**
21 | * Core Bluetooth's CBService instance
22 | */
23 | @property (strong, nonatomic, readonly) CBService *cbService;
24 |
25 | /**
26 | * NSString representation of 16/128 bit CBUUID
27 | */
28 | @property (weak, nonatomic, readonly) NSString *UUIDString;
29 |
30 | /**
31 | * Flag to indicate discovering characteristics or not
32 | */
33 | @property (assign, nonatomic, readonly, getter = idDiscoveringCharacteristics) BOOL discoveringCharacteristics;
34 |
35 | /**
36 | * Availabel characteristics for this service,
37 | * will be updated after discoverCharacteristicsWithCompletion: call
38 | */
39 | @property (strong, nonatomic) NSArray *characteristics;
40 |
41 | /**
42 | * Discoveres Input characteristics of this service
43 | * input uuids Array of CBUUID's that contain characteristic UUIDs which
44 | * we need to discover
45 | */
46 | @property (strong, nonatomic, readonly) RACCommand *discoverCharacteristicsCommand;
47 |
48 | // ----- Used for input events -----/
49 |
50 | - (void)handleDiscoveredCharacteristics:(NSArray *)aCharacteristics error:(NSError *)aError;
51 |
52 | /**
53 | * Used for input events
54 |
55 | @param aChar CBCharacteristic
56 | @return OKCharacteristic
57 | */
58 | - (OKCharacteristic *)wrapperByCharacteristic:(CBCharacteristic *)aChar;
59 |
60 | /**
61 | * @return Wrapper object over Core Bluetooth's CBService
62 | */
63 | - (instancetype)initWithService:(CBService *)aService;
64 |
65 | @end
66 |
--------------------------------------------------------------------------------
/OKBluetooth/Classes/OKService.m:
--------------------------------------------------------------------------------
1 | //
2 | // OKService.m
3 | // OKBluetooth
4 | //
5 | // Created by yuhanle on 2018/7/25.
6 | //
7 |
8 | #import "OKService.h"
9 |
10 | #import "CBUUID+StringExtraction.h"
11 | #if TARGET_OS_IPHONE
12 | #import
13 | #elif TARGET_OS_MAC
14 | #import
15 | #endif
16 | #import "OKCharacteristic.h"
17 | #import "OKPeripheral.h"
18 | #import "OKUtils.h"
19 | #import
20 |
21 | @interface OKService ()
22 |
23 |
24 | /**
25 | * Discoveres Subscriber characteristics of this service
26 | */
27 | @property (strong, nonatomic) id discoverCharacteristicsSubscriber;
28 |
29 | @end
30 |
31 | @implementation OKService
32 |
33 | /*----------------------------------------------------*/
34 | #pragma mark - Getter/Setter -
35 | /*----------------------------------------------------*/
36 |
37 | - (NSString *)UUIDString
38 | {
39 | return [self.cbService.UUID representativeString];
40 | }
41 |
42 | - (NSString *)description
43 | {
44 | NSString *org = [super description];
45 |
46 | return [org stringByAppendingFormat:@" UUIDString: %@", self.UUIDString];
47 | }
48 |
49 | /*----------------------------------------------------*/
50 | #pragma mark - Public Methods -
51 | /*----------------------------------------------------*/
52 |
53 | - (OKCharacteristic *)wrapperByCharacteristic:(CBCharacteristic *)aChar
54 | {
55 | OKCharacteristic *wrapper = nil;
56 | for (OKCharacteristic *discovered in self.characteristics) {
57 | if (discovered.cbCharacteristic == aChar) {
58 | wrapper = discovered;
59 | break;
60 | }
61 | }
62 | return wrapper;
63 | }
64 |
65 | #pragma mark - InitializeRAC -
66 |
67 | - (void)initializeRAC
68 | {
69 | @weakify(self);
70 | _discoverCharacteristicsCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(NSArray *input) {
71 | @strongify(self);
72 | return [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
73 | if (self.cbService.peripheral.state != CBPeripheralStateConnected) {
74 | [subscriber sendError:[OKUtils discoverErrorWithCode:kOKUtilsMissingServiceErrorCode message:kOKUtilsMissingServiceErrorMessage]];
75 | return nil;
76 | }
77 | self.discoverCharacteristicsSubscriber = subscriber;
78 | [self.cbService.peripheral discoverCharacteristics:input forService:self.cbService];
79 | return [RACDisposable disposableWithBlock:^{
80 | self.discoverCharacteristicsSubscriber = nil;
81 | }];
82 | }];
83 | }];
84 |
85 | RAC(self, discoveringCharacteristics) = [self.discoverCharacteristicsCommand executing];
86 | }
87 |
88 | /*----------------------------------------------------*/
89 | #pragma mark - Private Methods -
90 | /*----------------------------------------------------*/
91 |
92 | - (void)updateCharacteristicWrappers
93 | {
94 | NSMutableArray *updatedCharacteristics = [NSMutableArray new];
95 | for (CBCharacteristic *characteristic in self.cbService.characteristics) {
96 | [updatedCharacteristics addObject:[[OKCharacteristic alloc] initWithCharacteristic:characteristic]];
97 | }
98 | _characteristics = updatedCharacteristics;
99 | }
100 |
101 | /*----------------------------------------------------*/
102 | #pragma mark - Handler Methods -
103 | /*----------------------------------------------------*/
104 |
105 | - (void)handleDiscoveredCharacteristics:(NSArray *)aCharacteristics error:(NSError *)aError
106 | {
107 | _discoveringCharacteristics = NO;
108 | [self updateCharacteristicWrappers];
109 | #if OK_ENABLE_BLE_LOGGING != 0
110 | for (OKCharacteristic *aChar in self.characteristics) {
111 | OKLog(@"Characteristic discovered - %@", aChar.cbCharacteristic.UUID);
112 | }
113 | #endif
114 |
115 | if (aError) {
116 | [self.discoverCharacteristicsSubscriber sendError:aError];
117 | }else {
118 | [self.discoverCharacteristicsSubscriber sendNext:self];
119 | [self.discoverCharacteristicsSubscriber sendCompleted];
120 | }
121 | }
122 |
123 |
124 | /*----------------------------------------------------*/
125 | #pragma mark - Lifecycle -
126 | /*----------------------------------------------------*/
127 |
128 | - (instancetype)initWithService:(CBService *)aService
129 | {
130 | if (self = [super init]) {
131 | _cbService = aService;
132 | [self initializeRAC];
133 | }
134 | return self;
135 | }
136 |
137 | @end
138 |
--------------------------------------------------------------------------------
/OKBluetooth/Classes/OKUtils.h:
--------------------------------------------------------------------------------
1 | //
2 | // OKUtils.h
3 | // OKBluetooth
4 | //
5 | // Created by yuhanle on 2018/7/25.
6 | //
7 |
8 | #ifndef OK_BLE_SILENCE
9 | #ifdef DEBUG
10 | #define OK_ENABLE_BLE_LOGGING 1
11 | #else
12 | #define OK_ENABLE_BLE_LOGGING 0
13 | #endif
14 | #endif
15 |
16 | #if OK_ENABLE_BLE_LOGGING != 0
17 | #ifdef LOG_VERBOSE
18 | #define OKLog(...) DDLogInfo(__VA_ARGS__)
19 | #define OKLogError(...) DDLogError(__VA_ARGS__)
20 | #else
21 | #define OKLog(...) NSLog(__VA_ARGS__)
22 | #define OKLogError(...) NSLog(__VA_ARGS__)
23 | #endif
24 | #else
25 | #define OKLog(...) ((void)0)
26 | #define OKLogError(...) ((void)0)
27 | #endif
28 |
29 | #import
30 |
31 | #pragma mark - Error Domains -
32 |
33 | /**
34 | * Error domain for Write errors
35 | */
36 | extern NSString * const kOKUtilsWriteErrorDomain;
37 |
38 | /**
39 | * Error domain for Scan errors
40 | */
41 | extern NSString * const kOKUtilsScanErrorDomain;
42 |
43 | /**
44 | * Global error Message key
45 | */
46 | extern NSString * const kOKErrorMessageKey;
47 |
48 | #pragma mark - Error Codes -
49 |
50 | /**
51 | * Error code for write operation
52 | * Service was not found on peripheral
53 | */
54 | extern const NSInteger kOKUtilsMissingServiceErrorCode;
55 |
56 | /**
57 | * Error code for write operation
58 | * Characteristic was not found on peripheral
59 | */
60 | extern const NSInteger kOKUtilsMissingCharacteristicErrorCode;
61 |
62 | /**
63 | * Error code for write operation
64 | * Characteristic data is nil
65 | */
66 | extern const NSInteger kOKUtilsMissingCharacteristicDataErrorCode;
67 |
68 | /**
69 | * Error code for scan timeout
70 | */
71 | extern const NSInteger kOKUtilsScanTimeoutErrorCode;
72 |
73 | #pragma mark - Error Messages -
74 |
75 | /**
76 | * Error message for write operation
77 | * Service was not found on peripheral
78 | */
79 | extern NSString * const kOKUtilsMissingServiceErrorMessage;
80 |
81 | /**
82 | * Error message for write operation
83 | * Characteristic was not found on peripheral
84 | */
85 | extern NSString * const kOKUtilsMissingCharacteristicErrorMessage;
86 |
87 | /**
88 | * Error message for write operation
89 | * Characteristic data is nil
90 | */
91 | extern NSString * const kOKUtilsMissingCharacteristicDataErrorMessage;
92 |
93 | /**
94 | * Error message for scan timeout
95 | */
96 | extern NSString * const kOKUtilsScanTimeoutErrorMessage;
97 |
98 | @interface OKUtils : NSObject
99 |
100 | /**
101 | * Error Generators - Scan
102 |
103 | @param aCode errCode
104 | @param aMsg errMessage
105 | @return NSError
106 | */
107 | + (NSError *)scanErrorWithCode:(NSInteger)aCode message:(NSString *)aMsg;
108 | /**
109 | * Error Generators - Write
110 |
111 | @param aCode errCode
112 | @param aMsg errMessage
113 | @return NSError
114 | */
115 | + (NSError *)writeErrorWithCode:(NSInteger)aCode message:(NSString *)aMsg;
116 | /**
117 | * Error Generators - Read
118 |
119 | @param aCode errCode
120 | @param aMsg errMessage
121 | @return NSError
122 | */
123 | + (NSError *)readErrorWithCode:(NSInteger)aCode message:(NSString *)aMsg;
124 | /**
125 | * Error Generators - Discover
126 |
127 | @param aCode errCode
128 | @param aMsg errMessage
129 | @return NSError
130 | */
131 | + (NSError *)discoverErrorWithCode:(NSInteger)aCode message:(NSString *)aMsg;
132 |
133 | @end
134 |
--------------------------------------------------------------------------------
/OKBluetooth/Classes/OKUtils.m:
--------------------------------------------------------------------------------
1 | //
2 | // OKUtils.m
3 | // OKBluetooth
4 | //
5 | // Created by yuhanle on 2018/7/25.
6 | //
7 |
8 | #import "OKUtils.h"
9 |
10 | /**
11 | * Error domain for Write errors
12 | */
13 | NSString * const kOKUtilsWriteErrorDomain = @"OKUtilsWriteErrorDomain";
14 |
15 | /**
16 | * Error domain for Scan errors
17 | */
18 | NSString * const kOKUtilsScanErrorDomain = @"OKUtilsScanErrorDomain";
19 |
20 | /**
21 | * Error domain for Read errors
22 | */
23 | NSString * const kOKUtilsReadErrorDomain = @"OKUtilsReadErrorDomain";
24 |
25 | /**
26 | * Error domain for Discover errors
27 | */
28 | NSString * const kOKUtilsDiscoverErrorDomain = @"OKUtilsDiscoverErrorDomain";
29 |
30 |
31 | /**
32 | * Global key for providing errors of OKBluetooth
33 | */
34 | NSString * const kOKErrorMessageKey = @"msg";
35 |
36 | /**
37 | * Error code for write operation
38 | * Service was not found on peripheral
39 | */
40 | const NSInteger kOKUtilsMissingServiceErrorCode = 410;
41 |
42 | /**
43 | * Error code for write operation
44 | * Characteristic was not found on peripheral
45 | */
46 | const NSInteger kOKUtilsMissingCharacteristicErrorCode = 411;
47 |
48 | /**
49 | * Error code for write operation
50 | * Characteristic data is nil
51 | */
52 | const NSInteger kOKUtilsMissingCharacteristicDataErrorCode = 412;
53 |
54 | /**
55 | * Error code for scan timeout
56 | */
57 | const NSInteger kOKUtilsScanTimeoutErrorCode = 110;
58 |
59 | /**
60 | * Error message for write operation
61 | * Service was not found on peripheral
62 | */
63 | NSString * const kOKUtilsMissingServiceErrorMessage = @"Provided service UUID doesn't exist in provided pheripheral";
64 |
65 | /**
66 | * Error message for write operation
67 | * Characteristic was not found on peripheral
68 | */
69 | NSString * const kOKUtilsMissingCharacteristicErrorMessage = @"Provided characteristic doesn't exist in provided service";
70 |
71 | /**
72 | * Error message for write operation
73 | * Characteristic data is nil
74 | */
75 | NSString * const kOKUtilsMissingCharacteristicDataErrorMessage = @"Provided characteristic data is nil";
76 |
77 | /**
78 | * Error message for scan timeout
79 | */
80 | NSString * const kOKUtilsScanTimeoutErrorMessage = @"Scan peripherals timeOut, please retry later";
81 |
82 | /**
83 | * Timeout of connection to peripheral
84 | */
85 | const NSInteger kOKUtilsPeripheralConnectionTimeoutInterval = 30;
86 |
87 | @implementation OKUtils
88 |
89 | /*----------------------------------------------------*/
90 | #pragma mark - Error Generators -
91 | /*----------------------------------------------------*/
92 |
93 | + (NSError *)scanErrorWithCode:(NSInteger)aCode message:(NSString *)aMsg
94 | {
95 | return [NSError errorWithDomain:kOKUtilsScanErrorDomain
96 | code:aCode
97 | userInfo:@{kOKErrorMessageKey : aMsg}];
98 | }
99 |
100 | + (NSError *)writeErrorWithCode:(NSInteger)aCode message:(NSString *)aMsg
101 | {
102 | return [NSError errorWithDomain:kOKUtilsWriteErrorDomain
103 | code:aCode
104 | userInfo:@{kOKErrorMessageKey : aMsg}];
105 | }
106 |
107 | + (NSError *)readErrorWithCode:(NSInteger)aCode message:(NSString *)aMsg
108 | {
109 | return [NSError errorWithDomain:kOKUtilsReadErrorDomain
110 | code:aCode
111 | userInfo:@{kOKErrorMessageKey : aMsg}];
112 | }
113 |
114 | + (NSError *)discoverErrorWithCode:(NSInteger)aCode message:(NSString *)aMsg
115 | {
116 | return [NSError errorWithDomain:kOKUtilsDiscoverErrorDomain
117 | code:aCode
118 | userInfo:@{kOKErrorMessageKey : aMsg}];
119 | }
120 |
121 | @end
122 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://travis-ci.org/latehorse/OKBluetooth)
4 | [](https://cocoapods.org/pods/OKBluetooth)
5 | [](https://cocoapods.org/pods/OKBluetooth)
6 | [](https://cocoapods.org/pods/OKBluetooth)
7 | [](https://cocoapods.org/pods/OKBluetooth)
8 | [](https://cocoapods.org/pods/OKBluetooth)
9 | [](https://cocoapods.org/pods/OKBluetooth)
10 |
11 | > [中文介绍](https://latehorse.github.io/OKBluetooth/#/zh-cn/?id=okbluetooth)
12 |
13 | ## Example
14 |
15 | To run the example project, clone the repo, and run `pod install` from the Example directory first.
16 |
17 | ## Requirements
18 |
19 | * XCode 8.0+
20 | * iOS 8.0+
21 |
22 | ## Installation
23 |
24 | OKBluetooth is available through [CocoaPods](https://cocoapods.org). To install
25 | it, simply add the following line to your Podfile:
26 |
27 | ```ruby
28 | pod 'OKBluetooth'
29 | ```
30 |
31 | ## Author
32 |
33 | yuhanle, deadvia@gmail.com
34 |
35 | ## License
36 |
37 | OKBluetooth is available under the MIT license. See the LICENSE file for more info.
38 |
--------------------------------------------------------------------------------
/_Pods.xcodeproj:
--------------------------------------------------------------------------------
1 | Example/Pods/Pods.xcodeproj
--------------------------------------------------------------------------------
/gh-pages.sh:
--------------------------------------------------------------------------------
1 | git push origin -d gh-pages
2 | git branch -d gh-pages
3 | git checkout -b gh-pages
4 | git add -f Documents
5 | git commit -m 'fix: code style'
6 | git subtree push --prefix Documents origin gh-pages
7 | git checkout master
--------------------------------------------------------------------------------