iBeacon的第一篇(基於Swift實現)


低功耗藍牙技術現在幾乎是只能手機的標配。隨着這一技術的發展,蘋果在2013年WWDC大會上,蘋果推出iBeacon技術。該技術允許開發人員開發能夠使用iBeacon硬件傳感器的iOS應用程序,來為相應的應用程序提供更加精准的位置信息。2014年WWDC大會上,蘋果表示,對iBeacon技術進行了改善,借助該技術,應用程序現在能夠跟蹤到用戶所在的樓層的精確位置信息。

iBeacon的工作方式是Transmitter-Receiver,即基站-接收機模式的。基站?這個時候不要想到移動、聯通的那些大鐵塔。這個基站可以是一個運行着Bluetooth 4.0 LE的設備,也可以是經過配置的iPhone、iPad。iPhone4S和之后的iPhone、iPad3或之后的iPad,包括iPad mini都可以配置成iBeacon基站。

這里列舉個iBeacon的使用場景:在房屋中介中使用。米國一家技術公司把 iBeacon 安裝在要出售的房屋前,當用戶開車至此,不用下車就可以用中介的 APP 獲得此房屋所有相關信息和照片,不用打印及搜索。據說,效果還是很不錯的,大約有一半左右的用戶打開手機查看了相關信息。甚至於還有一個老外用iBeacon做了一個實景的經營游戲,點擊這里觀看。

一個基站主要有三部分標識:

1. UUID,形如:206A2476-D4DB-42F0-BF73-030236F2C756。用來標識某一個公司。比如,某個房地產公司的的全部的基站都用同一個UUID。

2. major,用來標識某一類的beacon。比如這個房地產公司的北京的房子都設定為1,上海的都設定為2。

3. minor,用來標識某一個特定的beacon。比如某棟樓的某個基站。

用這個房地產商開發的app就可以獲取到UUID,和后面的major值和minor值。當app進入beacon的區域,探測到UUID:”206A2476-D4DB-42F0-BF73-030236F2C756“,major為1,minor為20。那么就表明這個用戶在這個房地產商的北京樓盤的編號20的樓盤。這棟樓的說明文字、圖片或者視頻等就可以展現在用戶面前。

下面就進入我們的教程。不過前提是你要是蘋果的開發者,因為這個app需要在真機運行。然后要有2台設備,一個當基站,一個當接收機。最后,還得有一台能運行Xcode的電腦。

首先創建一個SingleView的項目,起個名字叫MyBeacon。然后把我們需要用到的Framework加進來,把CoreBluethooth.framework和CoreLocation.framework加到項目中。

創建一個Storybard,並在視圖中添加如下的UIController:

     (這是Xcode6創建的storyboard)

關於storyboard的細節就不多說了。下面新建兩個文件:TrackViewController和ConfigViewController。這時候會自動生成一個ViewControler類。分別把他們按照上圖所示對應到stroyboard的幾個ViewController中,並添加圖中所示的UILabel。然后把需要現實APP運行結果的UILabel作為IBOutlet。

TrackViewController的IBOutlet是這樣的:(以下的兩個文件都用的是Swift語言,如果你需要的話可以自行修改為OC語言)

ConfigViewController的IBOutlet是這樣的:

 

先搞定基站部分

在ConfigViewController中添加如下的屬性:

第一個CLBeaconRegin屬性,用來設置基站需要的proximityUUID,major和minor的給你信息。

第二個NSDictionary的屬性,用來獲取外設的數據。

第三個CBPeripheralManager的屬性,用來開啟基站的數據傳輸。

這些屬性后面的感嘆號是Swift里的一種語法。變量或者屬性的定義,在類型的后面可以是問號后者是感嘆號。他們都表示這個變量或者屬性可以是有值的也可以是空值nil(注意這里的nil和OC里的nil是兩回事)。但是使用問號的變量或者屬性需要在使用前判斷是否有值,如果有的話通過感嘆號取值。使用感嘆號定義的變量或者屬性則可以直接取值並使用。這里使用感嘆號定義屬性,這樣在使用的時候可以直接取值。詳細的使用會在代碼中體現。

要讓iOS設備開始傳輸信號還需要做些其他的事情。在viewDidLoad中調用幾個方法才能讓基站工作起來。首先調用的是initBeacon方法,之后是要把數據現實出來的setLabels方法。

在這里通過uuid字符串設定NSUUID實例。uuid字符串可以通過uuidgen命令在終端里生成。也可以通過代碼生成,但是這里沒有這個必要。然后其他的值major和minor分別設定為1,identifier設定為我們示例的名字。

下面我們就要考慮傳輸的問題了。在用戶點擊了傳輸按鈕之后開始使用設備的藍牙外設發出信號。方法如下:

在這個方法里,首先通過我們設定的beaconRegion屬性獲得相關的外設數據。這個數據是NSDictionary類型的。需要說明的是,這個類型可以在Swift中使用,但是和Swift本身的Dictionary是不兼容的。

之后,初始化了CBPeripheralManager屬性。這里簡單處理,設定queue和options為nil。為了設定代理為self需要實現CBPeripheralManagerDelegate protocol。

實現了這個protocol之后,還需要實現這個protocol的一個方法。這個方法在OC里來講是required的,所以必須實現。

外設的狀態中有兩個需要我們處理的。一個是PoweredOn一個PoweredOff。On了就讓peripheralManager開始對外發出信號。Off了就停止發出信號。所以呢,如果直接在transmitAction中調用代碼startAdvertising是設備對外發出信號的話,會報錯。Console會打印出”CBPeripheralManager is not Powered on“。所以我們在以上的代碼中在Pwoered On的時候再開始調用代碼發出信號。發出信號的時候呢,我們給了advertising方法傳入了從beaconRegion取出來的NSDictionary數據。這也是接收機辨識基站用到的數據。

之后我們把數據顯示在界面上。

這個方法很簡單,只要在viewDidLoad里調用setLabels方法就可以。全部設定在基站里的數據都會顯示在界面上了。其中包括uuid、major和minor,最后是我們設定的identifier。用Swift比用OC寫代碼是會少一些量,如果你對Swift足夠熟的話。比如,取得一個值的字符竄值的時候就不用[NSString stringWithFormat:"..."]這么麻煩的寫法了。

我們的app已經可以作為基站使用了。但是沒有接收機的話這個app可是一點都不好玩。在下面就開始處理接收機的部分。

接受iBeacon信息

接收iBeacon信號的底層功能已經在Core Location Framework里實現了。在iOS7里,可以自動識別你是否進入了一個區域以及其他的距離之類的信息。

下面在TrackViewController中處理,首先把CoreLocation庫的頭文件加進來。然后增加下面的兩個屬性:

CLBeaconRegion屬性是用來定義我們要尋找的beacon的。這個app只會接收到有同樣的UUID的的發射機發射的信號。

CLLocationManager屬性是用來建立位置服務並搜索beacon的。

在viewDidLoad方法中添加如下代碼:

首先,要初始化CLLocationManager,然后把代理設置為self。當然設定代理之前我么需要實現CLLocationManagerDelegate protocol,具體的就不寫出來了。然后調用initRegion方法,這個會在后面給出詳情。

首先創建UUID實例。用來初始化這個實例的的uuid字符串必須和基站的uuid字符串是一樣的,要不互相找不見。

然后初始化beaconRegion,proximityUUID就是前面創建的UUID實例,identitifer就是在基站中用到的identifier字符串。然后開始監測前面初始化出來的beaconRegion。調用代碼self.trackLocationManager.startMonitoringForRegion(self.beaconRegion)開始檢測。

接下來,我們需要監測這個app進入和離開區域的事件,代碼如下:

第一個方法是進入的,當你接收到基站信號的時候這個方法就開始執行(當然你要離基站足夠近)。然后調用locationManager的startRagingBeaconsInRegion方法,病傳入我們之前定義好的beaconRegion屬性。

第二個方法很簡單,就是在離開這個區域的時候就停止執行,調用locationManager的stopRagingBeaconsInRegion。

下面是didRangeBeacons方法:

首先判斷方法傳入的beacons數組的元素有多少。如果有0個的時候什么都不做。如果有一個或以上的話,這里簡單處理只取最后一個beacon來處理。

之后的代碼都只是從beacon中取出數據來現實在界面上,比如UUID,major,minor,accuracy和RSSI。這些值會隨着接收機和基站的距離不同以及基站的設置不同而一直改變。尤其accuracy和RSSI都是beacon用來現實距離的。其中proximity會顯示四個值:Unknown、Immediate、Near和Far。Immediate是半米以內,Near遠一些,Far更遠。RSSI是信號強度。

運行程序

現在這個app可以運行了。但是需要分別運行在兩台可以兼容低功耗藍牙的設備上。一台當基站一台當接收機。

當你從足夠遠(10~20米)的地方想基站的方向走的時候,一個你”didEnterRange”那個didRangeBeacons方法就會被調用,這是就可以從方法中傳入的beacon數組中取出值來現實在界面上。

但是有一點需要注意的是,我們處理的基站只有一個,所以每次都取出beacon數組的最后一個。如果有多個beacon基站的話就需要循環beacon數組的每一個元素,判斷這個元素的proximity是Immdiate還是Near還是Far等。

每次測試的時候都進入或者離開一個區域太麻煩。可以在viewDidLoad方法中添加一行代碼:

然后添加如下方法:

手動調用了didStartMonitoringForRegion,在這個方法中調用了trackLocationManager的startRangingBeaconsInRegion方法。這樣雖然不完美,但是足夠測試了。

下面是代碼列表:

ConfigViewController:

 1 import UIKit
 2 import CoreLocation
 3 import CoreBluetooth
 4 
 5 class ConfigViewController: UIViewController, CBPeripheralManagerDelegate {
 6     
 7     // properties
 8     @IBOutlet var uuidLabel: UILabel
 9     @IBOutlet var majorLabel: UILabel
10     @IBOutlet var minorLabel: UILabel
11     @IBOutlet var identityLabel: UILabel
12     @IBOutlet var transmitButton: UIButton
13     
14     var beaconRegion : CLBeaconRegion!
15     var beaconPeripheralData : NSDictionary!
16     var peripheralManager : CBPeripheralManager!
17     
18 
19     override func viewDidLoad() {
20         super.viewDidLoad()
21 
22         self.initBeacon()
23         self.setLabels()
24     }
25     
26     func initBeacon(){
27         self.beaconRegion = CLBeaconRegion(proximityUUID: NSUUID(UUIDString: "206A2476-D4DB-42F0-BF73-030236F2C756")
28             , major: 1, minor: 1, identifier: "com.mybeacon.region")
29     }
30     
31     func setLabels(){
32         self.uuidLabel.text = self.beaconRegion.proximityUUID.UUIDString
33         self.majorLabel.text = self.beaconRegion.major.stringValue
34         self.minorLabel.text = self.beaconRegion.minor.stringValue
35         self.identityLabel.text = self.beaconRegion.identifier
36     }
37 
38     override func didReceiveMemoryWarning() {
39         super.didReceiveMemoryWarning()
40         // Dispose of any resources that can be recreated.
41     }
42     
43     @IBAction func transmitAction(sender: UIButton) {
44         self.beaconPeripheralData = self.beaconRegion.peripheralDataWithMeasuredPower(nil)
45         self.peripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: nil)
46     }
47 
48     // delegate methods
49     func peripheralManagerDidUpdateState(peripheral: CBPeripheralManager!){
50         if peripheral.state == CBPeripheralManagerState.PoweredOn {
51             println("Powered on")
52             self.peripheralManager.startAdvertising(self.beaconPeripheralData)
53         }
54         else if peripheral.state == CBPeripheralManagerState.PoweredOff {
55             println("Powered off")
56             self.peripheralManager.stopAdvertising()
57         }
58     }
59 }

 

TrackViewController:

 1 import UIKit
 2 import CoreLocation
 3 
 4 class TrackViewController: UIViewController, CLLocationManagerDelegate {
 5 
 6     @IBOutlet var beaconLabel: UILabel
 7     @IBOutlet var uuidLabel: UILabel
 8     @IBOutlet var majorLabel: UILabel
 9     @IBOutlet var minorLabel: UILabel
10     @IBOutlet var accuracyLabel: UILabel
11     @IBOutlet var distanceLabel: UILabel
12     @IBOutlet var rssiLabel: UILabel
13     
14     var beaconRegion : CLBeaconRegion!
15     var trackLocationManager : CLLocationManager!
16     
17     override func viewDidLoad() {
18         super.viewDidLoad()
19 
20         self.trackLocationManager = CLLocationManager();
21         self.trackLocationManager.delegate = self;
22         self.initRegion()
23         self.locationManager(self.trackLocationManager, didStartMonitoringForRegion: self.beaconRegion)
24     }
25     
26     func initRegion(){
27         var uuid = NSUUID(UUIDString: "206A2476-D4DB-42F0-BF73-030236F2C756")
28         self.beaconRegion = CLBeaconRegion(proximityUUID: uuid, identifier: "com.mybeacon.region")
29         self.trackLocationManager.startMonitoringForRegion(self.beaconRegion)
30     }
31     
32     func locationManager(manager: CLLocationManager!, didStartMonitoringForRegion region: CLRegion!) {
33         self.trackLocationManager.startRangingBeaconsInRegion(self.beaconRegion)
34     }
35 
36     override func didReceiveMemoryWarning() {
37         super.didReceiveMemoryWarning()
38         // Dispose of any resources that can be recreated.
39     }
40     
41     func locationManager(manager: CLLocationManager!, didEnterRegion region: CLRegion!) {
42         self.trackLocationManager.startRangingBeaconsInRegion(self.beaconRegion)
43     }
44     
45     func locationManager(manager: CLLocationManager!, didExitRegion region: CLRegion!) {
46         self.trackLocationManager.stopRangingBeaconsInRegion(self.beaconRegion)
47         self.beaconLabel.text = "No"
48     }
49     
50     func locationManager(manager: CLLocationManager!, didRangeBeacons beacons: [AnyObject]!, inRegion region: CLBeaconRegion!) {
51         println("beacons count" + String(beacons.count))
52         if beacons.count <= 0 {
53             return
54         }
55         
56         var beacon: AnyObject = beacons[beacons.count - 1]
57         
58         self.beaconLabel.text = "Yes"
59         self.uuidLabel.text = beacon.proximityUUID ? beacon.proximityUUID!.UUIDString : ""
60         self.majorLabel.text = beacon.major ? beacon.major!.stringValue : ""
61         self.minorLabel.text = beacon.minor ? beacon.minor!.stringValue : ""
62         self.accuracyLabel.text = beacon.accuracy ? String(beacon.accuracy) : ""
63         if beacon.proximity {
64 
65             switch(beacon.proximity!){
66             case .Unknown:
67                 self.distanceLabel.text = "Unknown proximity"
68             case CLProximity.Immediate:
69                 self.distanceLabel.text = "Immediate"
70             case CLProximity.Near:
71                 self.distanceLabel.text = "Near"
72             case CLProximity.Far:
73                 self.distanceLabel.text = "Far"
74             default:
75                 println("default of switch-case")
76             }
77         }
78         self.rssiLabel.text = beacon.rssi ? beacon.rssi!.description : ""
79     }
80 }

 

 

 轉載請注明出處!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM