この記事では、BLE機能を持つマイコンにセンサーを繋げてIoTデバイスを作成、センサー値を自作スマホアプリで受け取れるところまで基礎的な内容を教えます。
提供するサンプルコードを改造することで様々なIoTデバイスが作成できるはずです!
準備するもの
- iPhone(BLE機能を備える4s以降の機種)
- 筆者がAndroid端末を使用したことがないため
- Mac
- アプリ開発にXcodeを使用するため
- Xcode
- アプリ開発用
- Arduino IDE
- マイコンのプログラミング用
- LightBlue
- BLEのUUIDを確認するため
- BLE機能を持つマイコン
-
Beetle BLE等の小型なものが望ましい
- お好みのセンサー
-
秋月電子のセンサー検索結果
以下の記事ではBeetle BLEの使用を前提に説明
マイコンの配線
VIN:電源の+線
GND:電源の-線、センサーのGNDピン
5V:センサーの電源ピン
A0:センサーの出力ピン
Arduino IDEの準備
ツールのボードからArduino Unoを選択
書き込みポートはUSBを選択
マイコンのプログラミング
Beetle BLEのボーレート:
115200bps
int data = 0;
void setup() {
Serial.begin(115200);
}
void loop() {
uint16_t value = analogRead(A0);
if (data == 0 && Serial.available() > 0) {
data = Serial.read();
if (data == 1) {
Serial.write((uint8_t*)&value, sizeof(value));
data = 0;
}
}
}
スマホから信号値1を受け取ると、アナログピンA0に繋がれたセンサーの値をスマホに返します。
XcodeとiPhoneの準備
Xcodeで作成したアプリを実機デバイスでテストする方法
上記記事を参考に準備してください
アプリ実装
LightBlueでBLEのUUIDを調べます。
デバイス名:Bluno
Beetle BLEのUUID:
DFB0:サービスUUID(端末と接続するのに使用)
DFB1:Serial通信(データのやり取りで使用)
DFB2:AT Ctrl(今回は使わない)
import UIKit
import CoreBluetooth
class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
// MARK: - プロパティ
var centralManager: CBCentralManager!
var connectedPeripheral: CBPeripheral?
var rxCharacteristic: CBCharacteristic?
// MARK: - IBOutlet
@IBOutlet weak var SSIDUILabel: UILabel!
// MARK: - ライフサイクル
override func viewDidLoad() {
super.viewDidLoad()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
// MARK: - CBCentralManagerDelegate メソッド
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
scanBLEModule()
case .poweredOff:
SSIDUILabel.text = "Bluetoothがオフです"
print("Bluetooth is Off")
default:
SSIDUILabel.text = "Bluetooth使用不能"
print("Bluetooth state is not supported: \(central.state.rawValue)")
}
}
func scanBLEModule() {
let serviceUUID = CBUUID(string: "DFB0")
centralManager.scanForPeripherals(withServices: [serviceUUID], options: nil)
SSIDUILabel.text = "スキャン中..."
print("Scanning for BLE devices...")
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
print("Discovered device: \(peripheral.name ?? "Unknown")")
if connectedPeripheral == nil {
connectedPeripheral = peripheral
connectedPeripheral?.delegate = self
centralManager.connect(peripheral, options: nil)
centralManager.stopScan()
SSIDUILabel.text = "接続中: \(peripheral.name ?? "Unknown Device")"
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("Connected to \(peripheral.name ?? "Unknown Device")")
SSIDUILabel.text = "接続済み: \(peripheral.name ?? "Unknown Device")"
peripheral.discoverServices([CBUUID(string: "DFB0")])
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
if let error = error {
print("Disconnected with error: \(error.localizedDescription)")
} else {
print("Disconnected from \(peripheral.name ?? "Unknown Device")")
}
SSIDUILabel.text = "切断されました"
connectedPeripheral = nil
}
// MARK: - CBPeripheralDelegate メソッド
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let error = error {
print("Service discovery failed: \(error.localizedDescription)")
return
}
if let services = peripheral.services {
for service in services {
if service.uuid == CBUUID(string: "DFB0") {
peripheral.discoverCharacteristics(nil, for: service)
}
}
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if let error = error {
print("Characteristic discovery failed: \(error.localizedDescription)")
return
}
if let characteristics = service.characteristics {
for characteristic in characteristics {
if characteristic.uuid == CBUUID(string: "DFB1") {
rxCharacteristic = characteristic
peripheral.setNotifyValue(true, for: characteristic)
print("RX Characteristic discovered and notifications enabled: \(characteristic)")
}
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
if let error = error {
print("Failed to enable notifications: \(error.localizedDescription)")
return
}
if characteristic.isNotifying {
print("Notifications enabled for characteristic: \(characteristic)")
} else {
print("Notifications disabled for characteristic: \(characteristic)")
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if let error = error {
print("Failed to receive data: \(error.localizedDescription)")
return
}
if let data = characteristic.value {
print("Data received: \(data as NSData)")
processData(data)
} else {
print("No data received.")
}
}
func processData(_ data: Data) {
if data.count == 2 {
let receivedValue = data.withUnsafeBytes { $0.load(as: UInt16.self) }
print("Received 2-byte UInt16 value: \(receivedValue)")
DispatchQueue.main.async {
self.SSIDUILabel.text = "受信データ: \(receivedValue)"
}
} else {
print("Invalid data size: \(data.count) bytes. Expected 2 bytes.")
DispatchQueue.main.async {
self.SSIDUILabel.text = "エラー: 不正なデータサイズ (\(data.count))"
}
}
}
// MARK: - デバイスへのデータ送信
func sendCommand(value: Int) {
guard let peripheral = connectedPeripheral,
let characteristic = rxCharacteristic else {
print("No connected device or RX characteristic.")
return
}
var val = value
let data = Data(bytes: &val, count: MemoryLayout<Int>.size)
peripheral.writeValue(data, for: characteristic, type: .withResponse)
print("Sent Int value: \(value)")
}
// MARK: - IBAction
@IBAction func sendButtonTapped(_ sender: UIButton) {
sendCommand(value: 1)
}
}
ボタンを押すとセンサー値の要求信号値1をデバイスに送ります。
受け取ったセンサー値をSSIDUILabelに表示します。
サンプルコードgitリンク
サンプルコードを利用する場合は、TeamとBundle identiferを入力してください。
おわりに
I先輩へ
この内容で土壌湿度センサーを繋げば、1年前に依頼されていた観葉植物のためのIoTデバイスが作れるかと思います。