●ドラッグコントロールを追加(iOS)

 前回開発したiPhoneアプリに、画面をドラッグして歩行をコントロールする機能を追加します。右手と左手の親指をロボットの両足に見立てて画面上を歩くような動作で操縦できます。

ドラッグコントロールモードにするためのボタンと指の動きを拾うための部品(dragView)は既に設置されていますので、Main.storyboardとViewController.swiftとの関連付けと行ってください。

    @IBOutlet weak var scanBtn: UIButton!
    @IBOutlet weak var connectBtn: UIButton!
    @IBOutlet weak var ltrnBtn: UIButton!
    @IBOutlet weak var fwrdBtn: UIButton!
    @IBOutlet weak var rtrnBtn: UIButton!
    @IBOutlet weak var leftBtn: UIButton!
    @IBOutlet weak var stopBtn: UIButton!
    @IBOutlet weak var rghtBtn: UIButton!
    @IBOutlet weak var ledonBtn: UIButton!
    @IBOutlet weak var bwrdBtn: UIButton!
    @IBOutlet weak var ledoffBtn: UIButton!
    @IBOutlet weak var sndBtn: UIButton!
    @IBOutlet weak var deviceNameLabel: UILabel!
    @IBOutlet weak var walkBtn: UIButton!
    @IBOutlet weak var dragView: UIView!
    
    @IBAction func tapScanBtn(_ sender: Any) {
        startScan()
    }
    @IBAction func tapConnectBtn(_ sender: Any) {
        self.centralManager.connect(blePeripheral, options: nil)
    }
    @IBAction func tapLtrnBtn(_ sender: Any) {
        write(byteValue: [1])
    }
    @IBAction func tapFwrdBtn(_ sender: Any) {
        write(byteValue: [2])
    }
    @IBAction func tapRtrnBtn(_ sender: Any) {
        write(byteValue: [3])
    }
    @IBAction func tapLeftBtn(_ sender: Any) {
        write(byteValue: [4])
    }
    @IBAction func tapStopBtn(_ sender: Any) {
        write(byteValue: [5])
    }
    @IBAction func tapRghtBtn(_ sender: Any) {
        write(byteValue: [6])
    }
    @IBAction func tapLedonBtn(_ sender: Any) {
        write(byteValue: [7])
    }
    @IBAction func tapBwrdBtn(_ sender: Any) {
        write(byteValue: [8])
    }
    @IBAction func tapLedoffBtn(_ sender: Any) {
        write(byteValue: [9])
    }
    @IBAction func tapSndBtn(_ sender: Any) {
        write(byteValue: [0])
    }
    @IBAction func tapWalkBtn(_ sender: Any) {
    }

 
 次にWALKボタンが押された時の処理とdragView上で指を動かした時の処理を追記します。

import UIKit
import CoreBluetooth

let kBLEService_UUID = "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
let kBLE_Characteristic_uuid_Tx = "beb5483e-36e1-4688-b7f5-ea07361b26a8"
let MaxCharacters = 20

let BLEService_UUID = CBUUID(string: kBLEService_UUID)
let BLE_Characteristic_uuid_Tx = CBUUID(string: kBLE_Characteristic_uuid_Tx)
var txCharacteristic : CBCharacteristic?

class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {

    private let NORMAL = 0
    private let WALK = 1
    var mode:Int = 0
    var counter:Int = 0
    @IBOutlet weak var scanBtn: UIButton!
    @IBOutlet weak var connectBtn: UIButton!
    @IBOutlet weak var ltrnBtn: UIButton!
    @IBOutlet weak var fwrdBtn: UIButton!
    @IBOutlet weak var rtrnBtn: UIButton!
    @IBOutlet weak var leftBtn: UIButton!
    @IBOutlet weak var stopBtn: UIButton!
    @IBOutlet weak var rghtBtn: UIButton!
    @IBOutlet weak var ledonBtn: UIButton!
    @IBOutlet weak var bwrdBtn: UIButton!
    @IBOutlet weak var ledoffBtn: UIButton!
    @IBOutlet weak var sndBtn: UIButton!
    @IBOutlet weak var deviceNameLabel: UILabel!
    @IBOutlet weak var walkBtn: UIButton!
    @IBOutlet weak var dragView: UIView!
    
    @IBAction func tapScanBtn(_ sender: Any) {
        startScan()
    }
    @IBAction func tapConnectBtn(_ sender: Any) {
        self.centralManager.connect(blePeripheral, options: nil)
    }
    @IBAction func tapLtrnBtn(_ sender: Any) {
        write(byteValue: [1])
        mode = NORMAL
        dragView.isUserInteractionEnabled = false
        dragView.backgroundColor = UIColor.lightGray
    }
    @IBAction func tapFwrdBtn(_ sender: Any) {
        write(byteValue: [2])
        mode = NORMAL
        dragView.isUserInteractionEnabled = false
        dragView.backgroundColor = UIColor.lightGray
    }
    @IBAction func tapRtrnBtn(_ sender: Any) {
        write(byteValue: [3])
        mode = NORMAL
        dragView.isUserInteractionEnabled = false
        dragView.backgroundColor = UIColor.lightGray
    }
    @IBAction func tapLeftBtn(_ sender: Any) {
        write(byteValue: [4])
        mode = NORMAL
        dragView.isUserInteractionEnabled = false
        dragView.backgroundColor = UIColor.lightGray
    }
    @IBAction func tapStopBtn(_ sender: Any) {
        write(byteValue: [5])
        mode = NORMAL
        dragView.isUserInteractionEnabled = false
        dragView.backgroundColor = UIColor.lightGray
    }
    @IBAction func tapRghtBtn(_ sender: Any) {
        write(byteValue: [6])
        mode = NORMAL
        dragView.isUserInteractionEnabled = false
        dragView.backgroundColor = UIColor.lightGray
    }
    @IBAction func tapLedonBtn(_ sender: Any) {
        write(byteValue: [7])
    }
    @IBAction func tapBwrdBtn(_ sender: Any) {
        write(byteValue: [8])
        mode = NORMAL
        dragView.isUserInteractionEnabled = false
        dragView.backgroundColor = UIColor.lightGray
    }
    @IBAction func tapLedoffBtn(_ sender: Any) {
        write(byteValue: [9])
    }
    @IBAction func tapSndBtn(_ sender: Any) {
        write(byteValue: [0])
    }
    @IBAction func tapWalkBtn(_ sender: Any) {
        mode = WALK
        dragView.isUserInteractionEnabled = true
        dragView.backgroundColor = UIColor.blue
    }
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        for touch in touches {
            let location = touch.location(in: dragView)
            Swift.print(location)
        }
    }
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        var byteArray:[UInt8] = [0x00, 0x00, 0x00, 0x00, 0x00]
        let width = dragView.frame.width
        let height = dragView.frame.height
        if(mode == WALK){
            byteArray[0] = 127
            for touch in touches {
                let location = touch.location(in: dragView)
                Swift.print(location)
                if(location.x > width/2){
                    byteArray[1] = 0
                }else{
                    byteArray[1] = 1
                }
                if(location.y < height*0.2){
                    byteArray[2] = 0
                }else if(location.y > height*0.8){
                    byteArray[2] = 100
                }else{
                    byteArray[2] = (UInt8)(100 * (location.y - height*0.2) / (height*0.6))
                }
                counter=counter+1
                if(counter%4==0){
                    write(byteValue: byteArray)
                }
            }
        }
    }
    
    var centralManager: CBCentralManager!
    var blePeripheral: CBPeripheral!
    var timer = Timer()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        centralManager = CBCentralManager(delegate: self, queue: nil)
        mode = NORMAL
        self.view.isMultipleTouchEnabled = true
    }

    func startScan() {
        print("Now Scanning...")
        self.timer.invalidate()
        centralManager.scanForPeripherals(withServices: nil , options: [CBCentralManagerScanOptionAllowDuplicatesKey:false])
        Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(self.cancelScan), userInfo: nil, repeats: false)
    }

    @objc func cancelScan() {
        self.centralManager.stopScan()
        print("Scan Stopped")
    }
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        print("discovered device : \(peripheral)")
        if peripheral.name == "MyESP32" {
            blePeripheral = peripheral
            self.centralManager.connect(blePeripheral, options: nil)
            cancelScan()
            deviceNameLabel.text = "MyESP32"
        }
    }
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        print("succeeded to connect")
        blePeripheral.delegate = self
        blePeripheral.discoverServices(nil)
    }
    
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        print("failed to connect")
        connectBtn.isUserInteractionEnabled = true
        connectBtn.backgroundColor = UIColor.blue
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if ((error) != nil) {
            print("Error discovering services: \(error!.localizedDescription)")
            return
        }
        guard let services = peripheral.services else {
            return
        }
        for service in services {
            peripheral.discoverCharacteristics(nil, for: service)
        }
        print("Discovered Services: \(services)")
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if ((error) != nil) {
            print("Error discovering services: \(error!.localizedDescription)")
            return
        }
        guard let characteristics = service.characteristics else {
            return
        }
        print("Found \(characteristics.count) characteristics!")
        for characteristic in characteristics {
            if characteristic.uuid.isEqual(BLE_Characteristic_uuid_Tx){
                txCharacteristic = characteristic
                print("Tx Characteristic: \(characteristic.uuid)")
                ltrnBtn.isUserInteractionEnabled = true
                ltrnBtn.backgroundColor = UIColor.blue
                fwrdBtn.isUserInteractionEnabled = true
                fwrdBtn.backgroundColor = UIColor.blue
                rtrnBtn.isUserInteractionEnabled = true
                rtrnBtn.backgroundColor = UIColor.blue
                leftBtn.isUserInteractionEnabled = true
                leftBtn.backgroundColor = UIColor.blue
                stopBtn.isUserInteractionEnabled = true
                stopBtn.backgroundColor = UIColor.blue
                rghtBtn.isUserInteractionEnabled = true
                rghtBtn.backgroundColor = UIColor.blue
                ledonBtn.isUserInteractionEnabled = true
                ledonBtn.backgroundColor = UIColor.blue
                bwrdBtn.isUserInteractionEnabled = true
                bwrdBtn.backgroundColor = UIColor.blue
                ledoffBtn.isUserInteractionEnabled = true
                ledoffBtn.backgroundColor = UIColor.blue
                sndBtn.isUserInteractionEnabled = true
                sndBtn.backgroundColor = UIColor.blue
                walkBtn.isUserInteractionEnabled = true
                walkBtn.backgroundColor = UIColor.blue
            }
        }
    }
    
    func write(byteValue: [UInt8]) {
        let data:[UInt8] = byteValue
        let writeData =  NSData(bytes: data, length: byteValue.count)
        if let blePeripheral = blePeripheral{
            if let txCharacteristic = txCharacteristic {
                print("write")
                blePeripheral.writeValue(writeData as Data, for: txCharacteristic, type: CBCharacteristicWriteType.withResponse)
            }
        }
    }
    
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        print("state: \(central.state)")
        if central.state == CBManagerState.poweredOn {
            print("Bluetooth Enabled")
            scanBtn.isUserInteractionEnabled = true
            scanBtn.backgroundColor = UIColor.blue
        } else {
        }
    }
}

 変更箇所の簡単な説明をします。

    private let NORMAL = 0
    private let WALK = 1
    var mode:Int = 0
    var counter:Int = 0

 ドラッグコントロールの時と通常のボタンによる操縦の場合とで一応モードを分けています。counterはドラッグ時の座標アップデートがあまり頻繁にならないようにするための対策に使っています。

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    var byteArray:[UInt8] = [0x00, 0x00, 0x00, 0x00, 0x00]
    let width = dragView.frame.width
    let height = dragView.frame.height
    if(mode == WALK){
        byteArray[0] = 127
        for touch in touches {
            let location = touch.location(in: dragView)
            Swift.print(location)
            if(location.x > width/2){
                byteArray[1] = 0
            }else{
                byteArray[1] = 1
            }
            if(location.y < height*0.2){
                byteArray[2] = 0
            }else if(location.y > height*0.8){
                byteArray[2] = 100
            }else{
                byteArray[2] = (UInt8)(100 * (location.y - height*0.2) / (height*0.6))
            }
            counter=counter+1
            if(counter%4==0){
                write(byteValue: byteArray)
            }
        }
    }
}

 dragView上をドラッグするとtouchesMovedイベントが呼ばれます。指を動かすとdragView上の座標が取得できます。座標が領域の右側か左側か、縦方向の位置を0-100の値に変換してコマンドとして送信します。コマンドはbyteArray[0]:WALKモードであることの識別値(127)、byteArray[1]:右か左か(右:1 左:0)、byteArray[2]:縦方向位置(0-100)となっていて、ESP32側で歩行プログラムの配列から関節角度を計算します。

drag_control
<= マイクロマシーン チュートリアル(15)
チュートリアルベース
マイクロマシーン チュートリアル (17) =>