やぎ星人です。こんにちは。
雨が続いておりますが、インドア派の私にはノーダメージです。フハハ。
今回の記事は少し長くなりそうなので早速本題へ。
今回やること
スマートカーテンデバイスとのBluetooth通信の実現方法について検討します。
と、その前に..
UIデザインの変更
BLE通信機能を実装する前に、UIデザインを少し変更することにしました。
現在、以下の3画面をTabビューにて表示しています。
- カーテン制御機能(開作動・閉作動・停止)画面(ContentView.swift)
- カーテン開閉時刻設定画面(AlarmView.swift)
- デバイス接続状態(BLE)画面(BleStateView.swift)
この中でデバイス接続状態(BLE)画面については、画面の上部に常に表示するようにしたいと考えました。(ポニ丸からの提案がきっかけ)
BLEの接続状態は各ビューから確認できたほうが良いかなという理由です。
途中で切断したことにも気づけますし。
と、いうわけでContentView.swiftファイルを修正していきます。
現在、VStackによりステータスバー(Colorビュー)とTabビューを縦並びに配置しているのですが間にBLEStateビューを挿入します。
コード修正ポイントは
- BLEStateビューをTabビューの子要素から削除
- BLEStateビューをステータスバーとTabビューの間に追加
- BLEStateビューサイズを調整
- BLEStateビュー内のUIパーツの表示位置、サイズを調整
といったところでしょうか。
コードの修正結果はこちらです。
//
// ContentView.swift
// SmartCurtain
//
// Created by yagi seijin on 2020/08/14.
// Copyright © 2020 Loose Life Hack. All rights reserved.
//
import SwiftUI
struct ContentView: View {
init(){
UITabBar.appearance().barTintColor = UIColor(named: "tab")
}
@State private var selection = 0
var body: some View {
GeometryReader{ geometry in
VStack(spacing: 1){
//ステータスバーと同位置・同サイズのColorビューを配置
Color.black
.frame(height: geometry.safeAreaInsets.top)
//デバイス接続状態(BLEStateビュー)
BleStateView()
.frame(height: geometry.size.height * 0.125)
//Tabビュー
TabView(selection: self.$selection){
//カーテン制御機能View
ControlView()
.font(.title)
.tabItem {
VStack {
Image(systemName: "gear")
Text("Control")
}
}.tag(0)
//カーテン開閉時刻設定View
AlarmView()
.font(.title)
.tabItem {
VStack {
Image(systemName: "alarm.fill")
Text("Alarm")
}
}.tag(1)
}
}} .edgesIgnoringSafeArea(.top)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
//
// BleStateView.swift
// SmartCurtain
//
// Created by yagi seijin on 2020/08/26.
// Copyright © 2020 Loose Life Hack. All rights reserved.
//
import SwiftUI
struct BleStateView: View {
var body: some View {
ZStack{
LinearGradient(gradient: Gradient(colors: [Color("bleview_top"), Color("bleview_bottom")]), startPoint: .top, endPoint: .bottom)
GeometryReader{ geometry in
HStack{
VStack(){
Image("wave04")
.resizable()
.scaledToFit()
.frame(height: geometry.size.height * 0.7)
}
VStack{
Spacer()
.frame(height: geometry.size.height * 0.5)
Text("デバイス検索中")
.bold()
.frame(width: geometry.size.width * 0.35, alignment: .leading)
}
VStack{
Spacer()
.frame(height: geometry.size.height * 0.2)
Image("searchBtn")
.renderingMode(.original)
.resizable()
.scaledToFit()
.frame(width: geometry.size.width * 0.25)
}
}
}
}
}
}
struct BleStateView_Previews: PreviewProvider {
static var previews: some View {
BleStateView()
}
}
そしてこちらが実行結果です。
BLEStateビューの背景色がしっくりこなくていろいろ試行錯誤した結果、グラデーションカラーに決めました。
グラデーションカラーについてはLinearGradient構造体を使用しました。
LinearGradient(gradient: Gradient, startPoint: UnitPoint, endPoint: UnitPoint)
gradient -> グラデーションで使用する色を配列で指定する
startPoint -> グラデーション開始点
endPoint -> グラデーション終了点
Core Bluetoothについて
さて、ここからがメインテーマです。
ポニ丸が開発するデバイスとBluetooth通信をしなければなりません。
まずは情報収集から。
ネットでいろいろと調査した結果、「Core Bluetooth」フレームワークを使用すれば良いという結論に至りました。
Core Bluetoothって何?BLE通信とは?
簡単にいうとiOSアプリケーションがBLE通信を実現するためのフレームワークです。
ちなみにBLE(Bluetooth Low Energy)通信については以下のとおり。
BLE(Bluetooth Low Energy)
・Bluetooth通信規格の中の1つ(Ver 4.0より追加された)
・【メリット】従来規格と比べ省電力、低コストに設計されている
・【デメリット】通信距離が短く、通信速度も遅い
当然、省電力の規格を使用したいところ。
スマホのバッテリーを過度に消耗するようなアプリは使いたくないですもんね。
ただ、デメリットが少し気になったので今回のシステム要件を満たすことができるか検討してみました。
通信速度が遅い点
Wikipediaの情報によると、通信速度は2Mbps〜125Kbpsと様々みたいです。
但し、これらはあくまでも理論値であり、現実的な通信速度は10Kbpsくらいとのこと。
スマートカーテンシステムにおいては、デバイスに制御指示を送信したり、デバイス状態を受信したりする際にBLE通信を使用します。データ転送量はせいぜい数byte程度ですし、実際にモータが作動するまでの多少のラグは気にならないところ。問題なしですね。
通信距離が短い点
通信速度については、Class1(約100m)、Class2(約10m)、Class3(約1m)と3つの規格があるそうですが、iPhoneがどの規格を採用しているかは公開されていないとのことでした。
普段の使用感からすると、少なくともClass3ではなさそうですね。(1mだとめっちゃ不便だし)
そうすると10m以上は保証されることになるので、余程の豪邸に住んでいない限りは大丈夫だと思います。但し、干渉物の影響等もあるので実際の動作確認でその辺りも検証したいと思います。
BLEについてもう少し詳しく知りたい
BLEの通信の仕組みについて、もう少し詳しく調べてました。
学習したことをOutputしたくて下記につらつらと記載していきます。
デバイスの役割について
BLE通信では、セントラルとペリフェラルという2つの役割があります。
セントラルを親局とし、それに対し様々な周辺機能(ペリフェラル)を接続するという構図となります。シリアル通信におけるマスタースレーブの関係と同じイメージです。
今回のシステムの場合は
セントラル・・・スマートフォン(やぎ星人)
ペリフェラル・・スマートカーテンデバイス(ポニ丸)
という役割分担となります。
ペアリングの流れ
セントラルとペリフェラルを1対1で通信させるためには、接続設定(ペアリング)が必要となります。
以下がペアリングの流れです。
- ペリフェラル(デバイス)側が自身の情報を発信します。このとき、ペリフェラルは後述するService UUID情報を提供します。
- セントラル(スマホ)側が周囲にどんなペリフェラルが存在するかスキャンにより確認します。
各ペリフェラルより発信されたService UUID情報を検出するイメージです。 - セントラル(スマホ)側より、「2」で検出したペリフェラルに対し接続を行います。
データの読み書きについて
Write
セントラル(スマホ)側より、ペリフェラル(デバイス)側のデータを書き換える処理です。
書き換え対象とするデータは後述するCharacteristic UUIDにて指定します。
Read
セントラル(スマホ)側より、ペリフェラル(デバイス)側のデータを読み出す処理です。
読み出し対象とするデータは後述するCharacteristic UUIDにて指定します。
Notify
ペリフェラル(デバイス)側より、セントラル(スマホ)側へデータ値を通知する処理です。
通知対象とするデータは後述するCharacteristic UUIDにて指定します。
データ定義
BLE通信によって読み書きするデータはCharacteristicという単位で構成されます。
実際に前述したWrite/Read/Notify処理はこのCharacteristic単位で行われます。
Characteristic内には、ValueとPropertyという要素が定義されています。
Value ・・・実際のデータ。読み書きはこのデータに対し行われます。
Property・・Write/Read/Notifyそれぞれ対応しているかを示す属性情報です。
さらにCharacteristicの集合体をServiceという単位で表現します。
ペアリング時はこのService単位で接続設定が行われます。
ServiceやCharacteristicはUUIDというユニークなIDで識別されます。
イメージ図です。
Core Bluetoothどう組み込めば良いの?
Core Bluetoothフレームワークを使用するにあたり、どのようなIFを実装する必要があるのか調査しました。ネット上のサンプルコードを読み漁り実装内容を読み解いていくという力技で学習しました。
- 「CoreBluetooth」フレームワークをimport
- プロトコル「CBCentralManagerDelegate」、「CBPeripheralDelegate」を継承したクラスを作成
- 「2」で作成したクラス内でCBCentralManagerクラスのインスタンスを生成
delegateパラメータに自身(self)を指定 - CBCentralManagerインスタンスよりコールされるデリゲートメソッドをいくつか実装
文章ではわかりにくいので、次回の記事で実際にCoreBluetoothを使用してみます。
おわりに
長くなりそうなので、今回はここまでとします。
CoreBluetoothというより、BLEの話がメインの記事になってしまいましたね..
CoreBluetoothの実装については次回の記事で詳細に触れていくので、是非ご覧ください。
それではまた次回の記事でお会いしましょう!
コメント