Scan and broadcast to nearby devices with Core Bluetooth

栏目: IT技术 · 发布时间: 4年前

内容简介:The topic of this post is about Bluetooth and how we can use it in our applications.Bluetooth technology can be used in a variety of ways, and in this post I am going to focus on how we can make our device broadcast its presence to nearby devices, and also

The topic of this post is about Bluetooth and how we can use it in our applications.

Bluetooth technology can be used in a variety of ways, and in this post I am going to focus on how we can make our device broadcast its presence to nearby devices, and also how we can scan for nearby devices that are running our app.

In order to implement this app, we are going to use Core Bluetooth , a framework to communicate between BLE devices provided by Apple.

Long story short, in this article, I am going to create a simple app to demonstrate how to use Core Bluetooth to implement the above mentioned scenario. The app will consists of a UITextField , where the user will enter the name for the device and two instances of UIButton . The one button will act as a trigger to start broadcasting, while the other will start the process of scanning other nearby devices.

Scan and broadcast to nearby devices with Core Bluetooth

Implementation

Before we dig deeper to the implementation, let’s take a look on some terminology. A BLE(Bluetooth Low Energy) device can be categorized into two roles: peripheral and central . A device acting as a peripheral advertises its presence, whereas a device acting as a central listens to these advertising packets. So in our case, we want our application to act both as a peripheral and a central.

With separation of concerns in mind, we are going to create a class that will handle the logic of broadcasting and scanning for devices using CoreBluetooth , and then we will see how we can use this class in a Catalyst application.

Before we jump to the actual implementation, let’s take some time to think about the desired behavior of this class and define the corresponding protocol .

First of all, we are going to need two functions that will be responsible for the initialization of the advertising and the scanning process. For the advertising one, we need to pass a String as a parameter, which is going to be used as the name of our device. Then, we have to somehow notify the uses of this class when a new device is discover. Maybe for this, we can use a delegate with a function that will be called once a new device is found and a hash map to store the peripherals.

protocol BluetoothManagerDelegate: AnyObject {
    func peripheralsDidUpdate()
}

protocol BluetoothManager {
    var peripherals: Dictionary<UUID, CBPeripheral> { get }
    var delegate: BluetoothManagerDelegate? { get set }
    func startAdvertising(with name: String)
    func startScanning()
}

Now that we have our protocol ready, we can move on to the actual implementation. Let’s create a new class named CoreBluetoothManager . This class will conform to the BluetoothManager protocol and will have the following content:

class CoreBluetoothManager: NSObject, BluetoothManager {
    // MARK: - Public properties
    weak var delegate: BluetoothManagerDelegate?
    private(set) var peripherals = Dictionary<UUID, CBPeripheral>() {
        didSet {
            delegate?.peripheralsDidUpdate()
        }
    }

    // MARK: - Public methods
    func startAdvertising(with name: String) {
        self.name = name
        peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
    }

    func startScanning() {
        centralManager = CBCentralManager(delegate: self, queue: nil)
    }

    // MARK: - Private properties
    private var peripheralManager: CBPeripheralManager?
    private var centralManager: CBCentralManager?
    private var name: String?
}

Please mind that you have to add import CoreBluetooth in the beginning of the file.

First, we describe the public properties for the delegate and the peripherals . When the value of the peripherals is updated, we call the peripheralsDidUpdate function to notify the delegate.

Then, we define the implementation for the func startAdvertising(with name: String) requirement. In this function, we initialize an instance of CBPeripheralManager which we will store in a private property. CBPeripheralManager is part of the CoreBluetooth framework and its primary function is to allow us to advertise to other devices.

For now the compiler will complain because we have set self as the delegate and it is not conforming to the CBPeripheralManagerDelegate protocol. We are going to fix this soon.

Moving on, we have the implementation for the startScanning requirement. In this case we initialize an instance of CBCentralManager which we also store in a private property. CBCentralManager is responsible for scanning for nearby BLE devices.

Since the compiler is complaining about the delegate, let’s try to implement them. First, let’s add an extension to the CoreBluetoothManager that will conform to the CBPeripheralManagerDelegate protocol and add the following content:

extension CoreBluetoothManager: CBPeripheralManagerDelegate {
    func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
        if peripheral.state == .poweredOn {
            if peripheral.isAdvertising {
                peripheral.stopAdvertising()
            }

            let uuid = CBUUID(string: Constants.SERVICE_UUID.rawValue)
            var advertisingData: [String : Any] = [
                CBAdvertisementDataServiceUUIDsKey: [uuid]
            ]

            if let name = self.name {
                advertisingData[CBAdvertisementDataLocalNameKey] = name
            }
            self.peripheralManager?.startAdvertising(advertisingData)
        } else {
            #warning("handle other states")
        }
    }
}

Here, we provide an implementation for the peripheralManagerDidUpdateState requirement. In the body of this function we check if the peripheral.state is .poweredOn as described on Apple’s documentation .

Before you call CBPeripheralManager methods, the peripheral manager object must be in the powered-on state, as indicated by the CBPeripheralManagerState.poweredOn . This state indicates that the device (your iPhone or iPad, for instance) supports Bluetooth low energy and that its Bluetooth is on and available for use.

After that, we check if the peripheral is already advertising and if it does, we stop it so that we can start advertising again with the new advertisingData which we are doing exactly after. advertisingData is a dictionary that contains the data to advertise. The supported advertising data types are CBAdvertisementDataServiceUUIDsKey and CBAdvertisementDataLocalNameKey . The first one is a UUID specific to our application and it will be used to filter out other BLE devices, whereas the second one is the local name of the peripheral.

For the service UUID, we add an enum named Constants , where we add a case for the SERVICE_UUID , like in the following snippet

enum Constants: String {
    case SERVICE_UUID = "4DF91029-B356-463E-9F48-BAB077BF3EF5"
}

Finally, we can call the startAdvertising function to initiate the advertising process.

Let’s move now to the scanning part of our class. We are going to add another extension to CoreBluetoothManager and this time it will conform to the CBCentralManagerDelegate protocol. The content of this extension will be the following:

extension CoreBluetoothManager: CBCentralManagerDelegate {
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if central.state == .poweredOn {

            if central.isScanning {
                central.stopScan()
            }

            let uuid = CBUUID(string: Constants.SERVICE_UUID.rawValue)
            central.scanForPeripherals(withServices: [uuid])
        } else {
            #warning("Error handling")
        }
    }

    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
        peripherals[peripheral.identifier] = peripheral
    }
}

In the same fashion as with the CBPeripheralManagerDelegate extension, we satisfy the centralManagerDidUpdateState requirement for the CBCentralManager this time, and again we are checking if the central.state is .poweredOn and if it is already scanning then we call the stopScan . Finally, we call scanForPeripherals and pass the CBUUID with the SERVICE_UUID that we are using for our application. This way, we are going to scan for devices that advertise this service and ignore all the others.

After starting the scanning process, we have to get informed when a device is discovered and for this reason, we add an implementation for the didDiscover requirement of the CBCentralManagerDelegate . There, we just update the peripherals dictionary with the peripheral that we have just discovered.

And that’s it regarding the BluetoothManager . Let’s now move on and see how we can use this class.

Application

As mentioned in the introduction, the application will have one screen which will contain a UITextField to enter the device name that we will use when calling the startAdvertising , and two instances of UIButton , one for triggering the advertising process and one for triggering the scanning process.

Since the post is about Core Bluetooth I will focus on how to use the class CoreBluetoothManager that we have just created and skip the UI part. The UI is created with UIKit, AutoLayout and programmatic views. Furthermore, the whole code can be found on GitHub and it’s a Catalyst application, meaning you can run it on iPhone, iPad or Mac.

To use the CoreBluetoothManager , we instantiate an instance of it in the SceneDelegate and we inject it to our only UIViewController , the BluetoothViewController .

// SceneDelegate.swift

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    private lazy var bluetoothManager = CoreBluetoothManager()
    .
    . 
    . 


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        .
        . 
        . 
        let vc = BluetoothViewController(bluetoothManager: bluetoothManager)
        .
        . 
        . 
    }
}

In the BluetoothViewController , we receive an instance of a class that conforms to the protocol BluetoothManager in the init function and we store it in a private property.

Then, we provide the implementations for the two buttons. For the first one, we use the bluetoothManager to call the startAdvertising and we pass the value of the UITextField as the device name. For the second button, we set the delegate of the bluetoothManager to self and call the startScanning function to trigger the scanning process.

As a result, we have to make BluetoothViewController conform to the protocol BluetoothManagerDelegate , by adding an extension and providing an implementation of the method peripheralsDidUpdate to fulfill BluetoothManagerDelegate ’s requirement. In this function, we just print the names of the peripherals on the console, but we could potentially reload an instance of a UITableView that would present the nearby devices.

// BluetoothViewController.swift

class BluetoothViewController: UIViewController {
    init(bluetoothManager: BluetoothManager) {
        self.bluetoothManager = bluetoothManager
        super.init(nibName: nil, bundle: nil)
    }

    .
    .
    .

    private var bluetoothManager: BluetoothManager

    @objc private func startAdvertising(sender: UIButton!) {
        nameTextField.resignFirstResponder()
        .
        .
        .

        bluetoothManager.startAdvertising(with: name)
    }

    @objc private func startScanning(sender: UIButton) {
        bluetoothManager.delegate = self
        bluetoothManager.startScanning()
    }

}

extension BluetoothViewController: BluetoothManagerDelegate {
    func peripheralsDidUpdate() {
        print(bluetoothManager.peripherals.mapValues{$0.name})
    }
}

And that’s about it for the application code. But before we run the application, we have to make some further adjustments, to enable the usage of Bluetooth.

First, we have to add an entry for the key NSBluetoothAlwaysUsageDescription to our Info.plist file, like in the following snippet

<key>NSBluetoothAlwaysUsageDescription</key>
    <string>Our app uses Bluetooth to find other devices</string>

Then, we have to select the target of the app, go to Signing & Capabilities > App Sandbox and enable the Bluetooth checkbox.

Scan and broadcast to nearby devices with Core Bluetooth

Lastly, make sure to enable the Bluetooth on the device that you are running the application. Scan and broadcast to nearby devices with Core Bluetooth

Now our app is ready!! Feel free to run it and start advertising and scanning for nearby devices! Scan and broadcast to nearby devices with Core Bluetooth

Conclusion

In this post, we have seen how to use the CoreBluetooth framework to advertise that an application is running on a device and also scan for nearby devices that are running the application. These concepts can be used as a base and build more complex application that will rely on Bluetooth and the communication of nearby devices, like for example a chat app, or maybe an AirDrop-like solution to share documents with other non-Apple devices.

Thanks for reading, I hope you find this post useful! Feel free to find me on Twitter and share your comments about this post!


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

智能商业

智能商业

曾鸣 / 中信出版集团 / 2018-11 / 68.00

《智能商业》由马云作序推荐。《智能商业》是阿里巴巴集团前总参谋长曾鸣,对互联网时代的重要趋势做出革命性解读的作品,披露了其对于未来商业模式的思考和判断。 2006年,曾鸣教授加入阿里巴巴集团,参与阿里巴巴集团及各重要业务线,如淘宝、支付宝、阿里云计算、菜鸟等的发展,被业界称为阿里的“军师”。 基于在阿里巴巴集团十几年的实践经验,以及对互联网、大数据和人工智能的深入思考,曾鸣教授在《智能......一起来看看 《智能商业》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

随机密码生成器
随机密码生成器

多种字符组合密码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具