［SwiftUI + Core Bluetooth］ソニーの IoT 機器「MESH」と iOS アプリを接続する

## 記事の概要
- ソニーが開発、販売している IoT 機器 「MESH」 と iOS アプリを接続する
![スクリーンショット 2022-12-04 15.25.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199441/7dbef5fc-611b-5a47-465e-8f7218c9f0ae.png)

## 今回の開発環境
### PC

|項目|内容|備考|
|---|---|---|
|PC|MacBook Air M1 （2020） メモリ：16GB ストレージ：1TB <br> macOS Ventura（ver 13.0）||
|iPhone|iPhone SE（2nd） iOS 16.1||
|IDE|Xcode ver 14.1||

### MESH
今回は 「ボタン」 を使用した

https://meshprj.com/jp/products/blocks/MESH-100BU.html

## アプリの内容
- MESH の 「ボタン」 を押すと、iOS アプリの画面に 「tapped.」 「double tapped.」 と表示する。

## コード （一部抜粋）
"BLEで複数デバイスとコネクトする - Qiita" 中で紹介されているコードを流用し、MESH が発した情報を取得する

※ 全体のコードは GitHub を参照してください

### コード （MESH との接続に関するもの）

```swift:BLEApp.swift
import SwiftUI
import CoreBluetooth

@main
struct BLEApp: App {
    @StateObject var viewModel: DeviceManager = DeviceManager()
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(viewModel)
        }
    }
}
```

```Device.swift
import Foundation
import CoreBluetooth

final class Device: NSObject {
    let peripheral: CBPeripheral
    let rssi: NSNumber
    
    init(peripheral: CBPeripheral, rssi: NSNumber){
        self.peripheral = peripheral
        self.rssi = rssi
        super.init()
        peripheral.delegate = self
    }
}

extension Device: CBPeripheralDelegate {}

```

```DeviceManager.swift
import Foundation
import CoreBluetooth

class DeviceManager: NSObject, ObservableObject {
    private let centralManager: CBCentralManager
    private let services:[CBUUID] = [CBUUID(string: MESH.UUID.description)]
    @Published var devices:[Device] = []
    @Published var recievedData:[Int] = []
    
    override init(){
        centralManager = CBCentralManager(delegate: nil, queue: nil)
        super.init()
        centralManager.delegate = self
        self.scan()
    }
    
    func scan(){
        centralManager.scanForPeripherals(withServices: services, options: nil)
    }
    
    func stopScan(){
        centralManager.stopScan()
    }
    
    func connect(perioheral: CBPeripheral){
        centralManager.connect(perioheral, options: nil)
    }
    
    func disconnect(peripheral: CBPeripheral){
        centralManager.cancelPeripheralConnection(peripheral)
    }
    
    func analyze(data: Data) -> [Int] {
        var result:[Int] = []
        data.map { i in result.append(Int(i)) }
        return result
    }
}

extension DeviceManager: CBCentralManagerDelegate {
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if central.state == .poweredOn {
            centralManager.scanForPeripherals(withServices: services)
        }
    }
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        let device = Device(peripheral: peripheral, rssi: RSSI)
        if let index = devices.firstIndex(where: { $0.peripheral.identifier == device.peripheral.identifier }) {
            devices[index] = device
        } else {
            devices.append(device)
        }
        devices.map { device in self.centralManager.connect(device.peripheral, options: nil)}
    }
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        guard let peripheral = devices.first?.peripheral else { return }
        peripheral.delegate = self
        peripheral.discoverServices(services)
        print("centralManager did connect peripheral: \(peripheral.name?.description ?? "unnnamed")")
    }
    
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        print("centralManager did disconnect peripheral")
    }
    
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        print("did update Value for executed")
        print(characteristic.description)
        guard let data = characteristic.value else { return }
        recievedData = analyze(data: data)
    }
    
}

extension DeviceManager: CBPeripheralDelegate {
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        for service in peripheral.services! {
            peripheral.discoverCharacteristics(nil, for: service)
        }
        print("did  DiscoverServices executed")
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        let byteArray: [UInt8] = [ 0x00, 0x02, 0x01, 0x03]
        let data = Data(byteArray)
        guard let characteristics = service.characteristics else { return }
        
        for characteristic in characteristics {
            peripheral.setNotifyValue(true, for: characteristic)
            peripheral.writeValue(data, for: characteristic, type: .withResponse)
            peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
        }
        print("did Discover Characteristics for executed")
    }
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        print("did Update Notification State For executed characteristic: \(characteristic.description)")
    }
}
```

```swift:MESHConstant.swift
import Foundation

enum MESH: String {
    case UUID = "72C90001-57A9-4D40-B746-534E22EC9F9E"
    var description: String {
        return rawValue
    }
}
```

### コード （iOS アプリの画面関連）
```swift:ContentView.swift
import SwiftUI
import CoreBluetooth

struct ContentView: View {
    @EnvironmentObject var manager: DeviceManager
    
    var body: some View {
        VStack {
            NavigationView {
                List(manager.devices,id:\.self){ device in
                    NavigationLink {
                        DeviceDetailView(device: device)
                    } label: {
                        DeviceListView(device: device)
                    }
                }
                .navigationTitle(Text("Peripherals"))
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
```

## GitHub

https://github.com/tanakadaichi1989/BLE

## 実際にアプリを実行 （YouTube）
開発した iOS アプリを MacBook Air （M1） 上で動かす

https://www.youtube.com/watch?v=SxwNb3zpFf4

## 参考資料
https://developer.apple.com/jp/xcode/swiftui/

https://developer.apple.com/documentation/corebluetooth

https://meshprj.com/jp/feature/index.html

https://developer.meshprj.com/hc/ja

https://qiita.com/hirotakan/items/569b4d63c95a3491b677

https://github.com/tanakadaichi1989/BLE