究極のハウツーガイド:20分でBluetooth対応Swiftアプリを構築する方法
前回のチュートリアルでは、Particle XenonアプリケーションにBluetooth機能を追加する方法を学びました。これにより、nRF ConnectやLightBlue ExplorerなどのテストアプリからオンボードのRGB LEDを操作できるようになりました。
今回はさらに一歩進んで、Particle MeshのRGB LEDを制御するSwiftアプリを開発します。順調に進めば、約20分で動作するアプリが完成するはずです!
それでは始めましょう。
今すぐ記事全文を読む時間がない方へ
PDF版はこちらからダウンロードできます。
事前準備
- Xcodeをインストールします。App Storeからダウンロードできます。
- Apple IDも必要です。筆者はiCloudのメールアドレスを使用しています。まだお持ちでない場合は、Xcode内で新規アカウントを作成できます。
- Particle MeshボードにRGBサンプルコードをインストールしておきます。
プロジェクトの作成
必要なものがすべて揃ったら、いよいよ楽しい作業に取り掛かりましょう!
Xcodeを開き、File → New Projectを選択します。

Single View Appを選択します。

次に、Project Nameをお好みの名前に変更します。筆者はOrganization Identifierをcom.jaredwolffに変更しました。ご自身に合わせて適宜変更してください。
保存先の場所を選択します。
続いて、Info.plistを見つけてください。

info.plistにPrivacy - Bluetooth Peripheral Usage Descriptionを追加します。
筆者が使用した説明文はApp uses Bluetooth to connect to the Particle Xenon RGB Exampleです。
この設定により、将来的にアプリをリリースする際にもBluetooth機能を利用できるようになります。
それでは、まず最小限の機能を動くようにしていきましょう!
最小限の機能を実装する

次に、デバイスへの接続とサービス検出を行う最小限の機能を持つアプリを作成します。処理の大部分はViewController.swiftで行います。
まずCoreBluetoothをインポートしましょう。
import CoreBluetooth
これにより、iOSのBluetooth Low Energy機能を制御できるようになります。次に、CBPeripheralDelegateとCBCentralManagerDelegateの両方をViewControllerクラスに追加します。
class ViewController: UIViewController, CBPeripheralDelegate, CBCentralManagerDelegate {
続いて、セントラルマネージャーとペリフェラルを保持するローカルのプライベート変数を作成します。詳細なセットアップは後ほど行います。
// Properties
private var centralManager: CBCentralManager!
private var peripheral: CBPeripheral!
viewDidLoad関数内で、centralManagerを初期化します。
centralManager = CBCentralManager(delegate: self, queue: nil)
delegate: selfの指定は非常に重要です。これを忘れると、起動時にセントラルの状態が更新されません。
先に進む前に、新しいファイルを作成し、ParticlePeripheral.swiftという名前を付けましょう。配置場所はどこでも構いませんが、後々のためにModelsという別のグループに格納しました。
その中に、Particleボード用のUUIDを含むパブリック変数を作成します。見覚えのあるUUIDのはずです!
import UIKit
import CoreBluetooth
class ParticlePeripheral: NSObject {
/// MARK: - Particle LED services and charcteristics Identifiers
public static let particleLEDServiceUUID = CBUUID.init(string: "b4250400-fb4b-4746-b2b0-93f0e61122c6")
public static let redLEDCharacteristicUUID = CBUUID.init(string: "b4250401-fb4b-4746-b2b0-93f0e61122c6")
public static let greenLEDCharacteristicUUID = CBUUID.init(string: "b4250402-fb4b-4746-b2b0-93f0e61122c6")
public static let blueLEDCharacteristicUUID = CBUUID.init(string: "b4250403-fb4b-4746-b2b0-93f0e61122c6")
}
ViewController.swiftに戻って、Bluetooth関連のコードを組み立てていきます。
Bluetooth関連の処理

Bluetoothに関する処理はすべてイベントベースです。これらのイベントを処理する複数の関数を定義していきます。重要なものは以下の通りです:
centralManagerDidUpdateStateは、Bluetoothペリフェラルのオン・オフが切り替わったときに呼び出されます。アプリ起動時にも発火するため、Bluetoothの状態を把握できます。スキャンの開始もここで行います。
centralManagerのdidDiscoverイベントは、スキャン結果を受信したときに発生します。これを使って接続を開始します。
centralManagerのdidConnectイベントは、デバイスとの接続が確立したときに発火します。ここでデバイス検出を開始します。注意: デバイス検出とは、利用可能なサービスやキャラクタリスティックを特定するプロセスのことです。接続先のデバイスの種類を確認する良い方法でもあります。
peripheralのdidDiscoverServicesイベントは、すべてのサービスの検出が完了したときに一度だけ発火します。ここで注目すべきは、接続が確立したため、centralManagerからperipheralに切り替わっている点です。ここでキャラクタリスティックの検出を開始します。ターゲットにはRGBサービスUUIDを使用します。
peripheralのdidDiscoverCharacteristicsForイベントは、指定されたサービスUUIDに基づいてすべてのキャラクタリスティックを提供します。これは完全なデバイス検出プロセスの最後のステップです。少し複雑ですが、接続フェーズ中に一度だけ行えば済みます!
すべてのBluetooth関数を定義する
どの関数・イベントがトリガーされるかがわかったところで、接続サイクルにおける論理的な順序に従って定義していきましょう。
まず、centralManagerDidUpdateStateを定義し、Particle RGB LEDサービスを持つデバイスのスキャンを開始します。Bluetoothが有効でない場合は何もしません。
// If we're powered on, start scanning
func centralManagerDidUpdateState(_ central: CBCentralManager) {
print("Central state update")
if central.state != .poweredOn {
print("Central is not powered on")
} else {
print("Central scanning for", ParticlePeripheral.particleLEDServiceUUID);
centralManager.scanForPeripherals(withServices: [ParticlePeripheral.particleLEDServiceUUID],
options: [CBCentralManagerScanOptionAllowDuplicatesKey : true])
}
}
次のステップは、centralManagerのdidDiscoverの定義です。このイベントが発生すれば、デバイスが見つかったことがわかります。
// Handles the result of the scan
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
// We've found it so stop scan
self.centralManager.stopScan()
// Copy the peripheral instance
self.peripheral = peripheral
self.peripheral.delegate = self
// Connect!
self.centralManager.connect(self.peripheral, options: nil)
}
self.centralManager.stopScan()でスキャンを停止し、peripheralを設定することでアプリ全体で参照できるようにします。そしてself.centralManager.connectでそのデバイスに接続します。
接続できたら、正しいデバイスと通信しているかを確認する必要があります。
// The handler if we do connect succesfully
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
if peripheral == self.peripheral {
print("Connected to your Particle Board")
peripheral.discoverServices([ParticlePeripheral.particleLEDServiceUUID])
}
}
2つのペリフェラルを比較することで、先ほど見つけたデバイスであることを確認できます。peripheral.discoverServiceでサービス検出を開始します。パラメータとしてParticlePeripheral.particleLEDServiceUUIDを渡せば、不要なサービスを拾うことはありません。
サービスの検出が完了すると、didDiscoverServicesイベントを受け取ります。「利用可能な」すべてのサービスを反復処理します(実際には1つだけですが!)。
// Handles discovery event
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let services = peripheral.services {
for service in services {
if service.uuid == ParticlePeripheral.particleLEDServiceUUID {
print("LED service found")
//Now kick off discovery of characteristics
peripheral.discoverCharacteristics([ParticlePeripheral.redLEDCharacteristicUUID,
ParticlePeripheral.greenLEDCharacteristicUUID,
ParticlePeripheral.blueLEDCharacteristicUUID], for: service)
return
}
}
}
}
この時点で、正しいサービスを確認するのは3回目です。キャラクタリスティックやサービスが多数存在するようになると、この確認作業が役立ちます。
peripheral.discoverCharacteristicsを呼び出し、探しているキャラクタリスティックのUUID配列を渡します。これらはすべてParticlePeripheral.swiftで定義したUUIDです。
最後に、didDiscoverCharacteriscsForイベントを処理します。利用可能なすべてのキャラクタリスティックを反復処理しながら、目的のものと比較していきます。
// Handling discovery of characteristics
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if let characteristics = service.characteristics {
for characteristic in characteristics {
if characteristic.uuid == ParticlePeripheral.redLEDCharacteristicUUID {
print("Red LED characteristic found")
} else if characteristic.uuid == ParticlePeripheral.greenLEDCharacteristicUUID {
print("Green LED characteristic found")
} else if characteristic.uuid == ParticlePeripheral.blueLEDCharacteristicUUID {
print("Blue LED characteristic found");
}
}
}
}
これで、Particle Meshデバイスの完全なデバイス検出を行う準備が整いました。次のセクションでは、ここまでの実装が正しく動作しているかテストします。
最小構成サンプルのテスト

始める前に、問題が発生した場合に備えてトラブルシューティングの手順を脚注にまとめてあります。
テストには、Bluetooth Low Energy対応のiPhoneが必要です。最近のiPhoneのほとんどが対応しています。非対応だった最後のモデルはiPhone 4か3GSだと思われます(なので、おそらく問題ないでしょう)。
まず、iPhoneをコンピュータに接続します。
再生ボタンと停止ボタンのある上部バーに移動し、ターゲットデバイスを選択します。筆者の場合は自分のiPhone(Jared's iPhone)を選択しました。iPadを使用することもできます。

次に、Command + Rキーまたは再生ボタンを押して、アプリをスマホに転送します。
ログタブが開いていることを確認してください。右上の下部ペインボタンをクリックすると有効化できます。

Meshデバイスがセットアップされ、サンプルコードが動作していることも確認してください。詳しくはこちらの記事をご覧ください。なお、Particle MeshボードでBluetoothを動作させるには、Device OS 1.3.0以降が必要です!
ファームウェアとアプリの両方が書き込まれたら、ログ出力を確認しましょう。
以下のような表示になるはずです:
View loaded
Central state update
Central scanning for B4250400-FB4B-4746-B2B0-93F0E61122C6
Connected to your Particle Board
LED service found
Red LED characteristic found
Green LED characteristic found
Blue LED characteristic found
これは、スマホが接続され、LEDサービスを発見できたことを意味します! キャラクタリスティックの検出も重要です。これがなければ、Meshデバイスにデータを送信できません。
次のステップでは、RGB値をリアルタイムで更新できるスライダーを作成します。
左にスライド、右にスライド
次に、Main.storyboardにいくつかのUI要素を追加していきます。Main.storyboardを開き、View Controllerの下にあるViewをクリックします。

次に、Libraryボタンをクリックします(旧ホームボタンのようなAppleのアートワークのアイコンです)。

アプリに挿入できるすべての選択肢がポップアップ表示されます。

Labelを3つドラッグし、Sliderを3つコピーしてビューに配置します。

ラベルをダブルクリックすると、名前を変更できます。

クリックしたままにすると、便利な整列ツールがポップアップ表示されます。中央にスナップすることもあります!

すべてを選択して一緒に移動することもできます。縦横に整列させましょう。
画面中央に固定するために、自動サイズ変更のプロパティを削除します。右上の定規アイコンをクリックし、それぞれの赤いバーをクリックします。これでラベルとスライダーが画面上に留まるようになります!

次に、Show Assistant Editorボタンをクリックします(ベン図のようなアイコンです)。

注意: アシスタントエディターでViewController.swiftが開いていることを確認してください。

そして、/propertiesセクションの下で、Controlキーを押しながら赤いスライダーをコードにドラッグします。

他のスライダーも同様に繰り返します。必ず異なる名前を付けてください。完了すると、コードは以下のようになります:
// Properties
private var centralManager: CBCentralManager!
private var peripheral: CBPeripheral!
// Sliders
@IBOutlet weak var redSlider: UISlider!
@IBOutlet weak var greenSlider: UISlider!
@IBOutlet weak var blueSlider: UISlider!
これでスライダーの値にアクセスできるようになります。
次に、各スライダーにValue Changedイベントを紐付けます。フォルダビューで赤いスライダーを右クリックしてください。

イベントの選択肢が表示されます。Value Changedイベントをクリックしてコードにドラッグします。わかりやすい名前を付けてください。筆者は赤いスライダーにRedSliderChangedと名付けました。
あと2回繰り返します。このステップの終わりには、コードは以下のようになっているはずです:
@IBAction func RedSliderChanged(_ sender: Any) {
}
@IBAction func GreenSliderChanged(_ sender: Any) {
}
@IBAction func BlueSliderChanged(_ sender: Any) {
}
また、筆者は各スライダーを選択してEnabledのチェックを外しました。こうすることでスライダーを動かせなくなります。有効化は後ほどコードで行います。

さらに、このタイミングで最大値を255に変更しておきましょう。デフォルト値も0.5から0に設定します。

ファイルの先頭に戻り、各キャラクタリスティック用のローカル変数を作成します。これらを使って、スライダーの値をParticle Meshボードに書き込みます。
// Characteristics
private var redChar: CBCharacteristic?
private var greenChar: CBCharacteristic?
private var blueChar: CBCharacteristic?
それでは、すべてを結び付けましょう!
didDiscoverCharacteristicsForコールバック関数内で、キャラクタリスティックを割り当てます。例えば以下のように:
if characteristic.uuid == ParticlePeripheral.redLEDCharacteristicUUID {
print("Red LED characteristic found")
redChar = characteristic
各キャラクタリスティックが見つかるたびに、同じ箇所でスライダーを有効化することもできます。
// Unmask red slider
redSlider.isEnabled = true
最終的に、didDiscoverCharacteristicsForは以下のようになります:
// Handling discovery of characteristics
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if let characteristics = service.characteristics {
for characteristic in characteristics {
if characteristic.uuid == ParticlePeripheral.redLEDCharacteristicUUID {
print("Red LED characteristic found")
redChar = characteristic
redSlider.isEnabled = true
} else if characteristic.uuid == ParticlePeripheral.greenLEDCharacteristicUUID {
print("Green LED characteristic found")
greenChar = characteristic
greenSlider.isEnabled = true
} else if characteristic.uuid == ParticlePeripheral.blueLEDCharacteristicUUID {
print("Blue LED characteristic found");
blueChar = characteristic
blueSlider.isEnabled = true
}
}
}
}
次に、RedSliderChanged、GreenSliderChanged、BlueSliderChanged関数を更新します。ここでやりたいのは、Changed関数に関連付けられたキャラクタリスティックを更新することです。筆者はwriteLEDValueToCharという独立した関数を作成しました。キャラクタリスティックとデータを引数として渡します。
private func writeLEDValueToChar( withCharacteristic characteristic: CBCharacteristic, withValue value: Data) {
// Check if it has the write property
if characteristic.properties.contains(.writeWithoutResponse) && peripheral != nil {
peripheral.writeValue(value, for: characteristic, type: .withoutResponse)
}
}
次に、各Changed関数にwriteLEDValueToCharの呼び出しを追加します。値をUInt8にキャストする必要があります(Particle Meshデバイスは符号なし8ビット整数を想定しています)。
@IBAction func RedSliderChanged(_ sender: Any) {
print("red:",redSlider.value);
let slider:UInt8 = UInt8(redSlider.value)
writeLEDValueToChar( withCharacteristic: redChar!, withValue: Data([slider]))
}
GreenSliderChangedとBlueSliderChangedにも同様に実装してください。それぞれredをgreen、blueに変更することを忘れずに!
最後に、コードを整理するために、Bluetooth切断を処理する関数も追加しました。
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
その内部では、スライダーの状態を0にリセットし、無効化します。
if peripheral == self.peripheral {
print("Disconnected")
redSlider.isEnabled = false
greenSlider.isEnabled = false
blueSlider.isEnabled = false
redSlider.value = 0
greenSlider.value = 0
blueSlider.value = 0
self.peripheralをnilにリセットしておくのも良い習慣です。そうすれば、存在しないデバイスに書き込もうとすることがなくなります。
self.peripheral = nil
そして、切断されたので、再度スキャンを開始しましょう!
// Start scanning again
print("Central scanning for", ParticlePeripheral.particleLEDServiceUUID);
centralManager.scanForPeripherals(withServices: [ParticlePeripheral.particleLEDServiceUUID],
options: [CBCentralManagerScanOptionAllowDuplicatesKey : true])
}
さて! テストの準備がほぼ整いました。次の(そして最後の)ステップに進みましょう。
スライダーのテスト

大変な作業は終わりです。いよいよ遊ぶ時間です!
すべてをテストする最も簡単な方法は、左上の再生ボタンをクリックするか、Command + Rショートカットキーを使うことです。Xcodeがスマホにアプリを読み込みます。白い画面の後に、スライダーが表示された画面が現れるはずです。
スライダーは、Particle Meshボードに接続するまでグレーアウトされたままです。ログ出力で接続が確立されたかどうかを確認できます。
View loaded
Central state update
Central scanning for B4250400-FB4B-4746-B2B0-93F0E61122C6
Connected to your Particle Board
LED service found
Red LED characteristic found
Green LED characteristic found
Blue LED characteristic found
(見覚えがありますか? 接続成功です!)
すべての手順を完璧にこなしていれば、スライダーを動かせるはずです。さらに、Particle Meshボード上のRGB LEDの色も変化するでしょう。

まとめ
この記事では、Particle MeshボードとiOSデバイスをBluetoothで接続する方法を学びました。利用可能な各キャラクタリスティックへの接続方法に加えて、それらを操作するための洗練されたインターフェースの作成方法まで習得できました。
ご想像の通り、iOSでのBluetooth開発は奥が深い世界です。近日公開予定のガイドThe Ultimate Guide to Particle Meshで、さらに詳しく解説する予定です。メーリングリストの購読者は、公開前の限定コンテンツへのアクセスと、発売時の割引特典を受けられます! こちらから登録してください。
ソースコード
完全なソースコードはGithubで公開しています。役に立ったと思ったら、スターをつけていただけると嬉しいです。⭐️
-
究極のハウツーガイド:20分でBluetooth対応Swiftアプリを構築する方法
前回のチュートリアルでは、Particle XenonアプリケーションにBluetooth機能を追加する方法を学びました。これにより、nRF ConnectやLightBlue ExplorerなどのテストアプリからオンボードのRGB LEDを操作できるようになりました。今回はさらに一歩進んで、Particle MeshのRGB LEDを制御するSwiftアプリを開発します。順調に進めば、約20分で動作するアプリが完成するはずです!それでは始めましょう。今すぐ記事全文を読む時間がない方へPDF版はこちらからダウンロードできます。事前準備Xcodeをインストールします。App Storeからダウ
-
先延ばしグセを克服!スマホで使えるおすすめ集中アプリ5選
私たちは日常のさまざまな場面で、ついSNSアプリを開いてしまいがちです。スマートフォンは生活に欠かせない存在となりましたが、やるべき作業を後回しにする原因になっていることも少なくありません。 物事を先延ばしにする癖は大きな問題であり、この習慣があると目標達成は遠のいてしまいます。スマホの利用が増えた現代では、この傾向はさらに顕著になっています。しかし興味深いことに、解決策もまたスマホの中にあります。先延ばし癖を克服するためのアプリが数多く存在するのです。 スマホへの依存は、家族や友人と過ごす時間の大切さを見失わせるほど私たちを変えてしまいました。だからこそ、この習慣を断ち切り、時間をより賢く使