BLEタグとiPhoneでつくるオレオレAmazon Dash Button

  • 4
    Like
  • 0
    Comment

Amazon Dash Buttonをハックして自分専用に作り変えるネタが流行ってますが、もう少し簡単にできないものかと、思いまして作ってみました。

シナリオ

  1. 会議室のホワイトボードマーカーが書けなくなったことに気がつく
  2. ホワイトボードの近くの小さなBLEタグのボタンを押す
  3. Kintoneへレコードが登録される
  4. 総務部の人がそのレコードを見てホワイトボードマーカーを補充
  5. 総務部の人はKintoneのレコードを削除する

材料

  1. ボタン付きBLEタグ:iTag 833円(https://www.amazon.co.jp/dp/B017SC9U76?th=1)
  2. お手持ちのiPhone

原理

BLE通信でボタン付きBLEタグのボタンクリックイベントを拾うだけ

BLEのServiceやCharacteristicはどうやって調べたの?

下記のアプリを使うとあっという間に分かります。

BLExplr
https://itunes.apple.com/jp/app/blexplr/id524018027?mt=8

コード

コンセプト通り機能するかを確認するレベルの内容ですが、こんな感じです。

ViewController.swift
import UIKit
import CoreBluetooth
import Alamofire

class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate  {

    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
    var centralManager: CBCentralManager!
    var tag: CBPeripheral!
    let kintoneBaseURL   = "https://<Kintoneのドメイン>/k/v1"
    let kintoneAppID     = /* <KintoneアプリのID> */
    let kintoneToken     = /* <KIntoneアプリのアクセストークン> */
    let blueTagUUID      = /* <iTagのUUID> */
    let blueTagServiceID = /* <iTagのService ID> */
    let blueTagCharID    = /* <iTagのCharacteristic ID> */

    override func viewDidLoad() {
        super.viewDidLoad()
        self.centralManager = CBCentralManager(delegate: self, queue: nil, options: nil)
        label.isHidden = true
        activityIndicator.startAnimating()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }


    // CentralManager状態の受信
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch (central.state) {
        case .poweredOff:
            NSLog("BLE PoweredOff")
        case .poweredOn:
            NSLog("BLE PoweredOn")
            central.scanForPeripherals(withServices: nil, options: nil)
        case .resetting:
            NSLog("BLE Resetting")
        case .unauthorized:
            NSLog("BLE Unauthorized")
        case .unknown:
            NSLog("BLE Unknown")
        case .unsupported:
            NSLog("BLE Unsupported")
        }
    }

    // Peripheral探索結果の受信と接続
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber){
        NSLog("name: \(peripheral.name), UUID: \(peripheral.identifier.uuidString), adData: \(advertisementData), RSSI: \(RSSI)")

        if(peripheral.identifier.uuidString == blueTagUUID){
            self.centralManager.stopScan()
            self.tag = peripheral
            self.centralManager.connect(self.tag, options: nil)
        }
    }

    // Peripheralへの接続結果の受信(成功時)
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        NSLog("connection success: \(peripheral.identifier.uuidString)")
        peripheral.delegate = self

        // サービスを探索
        let UUID = CBUUID(string: blueTagServiceID)
        peripheral.discoverServices([UUID])
    }

    // Peripheralへの接続結果の受信(失敗時)
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        NSLog("connection failed: \(peripheral.identifier.uuidString)")
    }

    // Service探索結果の受信
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if (error != nil) {
            NSLog("error: \(error)")
            return
        }

        for service in peripheral.services!
        {
            NSLog("found service: \(service)")

            //Characterristicを探索
            let UUID = CBUUID(string: blueTagCharID)
            peripheral.discoverCharacteristics([UUID], for:service as CBService)
        }
    }

    // Characterristic探索結果の受信
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?){
        if (error != nil) {
            NSLog("error: \(error)")
            return
        }

        for characteristic in service.characteristics! {
            NSLog("found characteristic: \(characteristic.uuid)")
            if(characteristic.uuid.uuidString == blueTagCharID){
                peripheral.setNotifyValue(true, for: characteristic)
            }
        }
    }

    // データ更新通知受け取り開始/停止結果を取得する
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        if let error = error {
            NSLog("error: \(error)")
        } else {
            NSLog("isNotifying: \(characteristic.isNotifying)")
            activityIndicator.hidesWhenStopped = true
            activityIndicator.stopAnimating()
            label.isHidden = false
        }
    }

    // データ更新通知を受け取る = ボタンが押された際に発生するイベント
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if let error = error {
            NSLog("error: \(error)")
            return
        }

        NSLog("value updated. UUID: \(characteristic.uuid.uuidString), value: \(characteristic.value)")

        if(label.text == "Push me!"){
            label.text = "Hello!"
        }else{
            label.text = "Push me!"
        }
        sendKintone()
    }

    func sendKintone(){
        let headers: HTTPHeaders = [
            "X-Cybozu-API-Token":kintoneToken
        ]

        let params: Parameters = [
            "app": kintoneAppID,
            "record": [
                "アイテム" : ["value" : "会議室A:ホワイトボードマーカー(黒)"],
                "数量"    : ["value" : "2"],
            ]
        ]

        _ = Alamofire.request("\(kintoneBaseURL)/record.json", method: .post, parameters: params, encoding: JSONEncoding.default, headers: headers).responseJSON{ resp in

            switch resp.result {
            case .success:
                NSLog("Sent")
            case .failure(let error):
                NSLog(error as! String)
            }
        }
    }
}

参考にした記事

[Swift][初心者]BLE接続するための情報まとめ
Core Bluetooth with Swift (ObjCのおまけ付き)