ルービックキューブロボットのソフトウェアをアップデートした 5. 機械操作(Python)

# この記事はなに？
私は現在2x2x2ルービックキューブを解くロボットを開発中です。これはそのロボットのプログラムの解説記事集です。
![soltvvo3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494375/1574c772-6b93-bfc3-6603-739b5e8c927f.jpeg)
かつて[こちら](https://qiita.com/Nyanyan_Cube/items/60f3699db96dba4a4bf5)の記事に代表される記事集を書きましたが、この時からソフトウェアが大幅にアップデートされたので新しいプログラムについて紹介しようと思います。

該当するコードは[こちら](https://github.com/Nyanyan/soltvvo/tree/master/Soltvvo3.2)で公開しています。

# 関連する記事集
「ルービックキューブを解くロボットを作ろう！」
1. [概要編](https://qiita.com/Nyanyan_Cube/items/a1b6e6bc7e4ac832b3d0)
2. [アルゴリズム編](https://qiita.com/Nyanyan_Cube/items/60f3699db96dba4a4bf5)
3. [ソフトウェア編](https://qiita.com/Nyanyan_Cube/items/94bfa7b56c8ff69d94cd)
4. [ハードウェア編](https://qiita.com/Nyanyan_Cube/items/8bb5555497e745697412)

ルービックキューブロボットのソフトウェアをアップデートした
1. [基本関数](https://qiita.com/Nyanyan_Cube/items/e225e8e31897d81a1401)
2. [事前計算](https://qiita.com/Nyanyan_Cube/items/495a32adf4bfe970e100)
3. [解法探索](https://qiita.com/Nyanyan_Cube/items/7413ceaad019a931112d)
4. [状態認識](https://qiita.com/Nyanyan_Cube/items/8fba84c078e3d6bd09f9)
5. 機械操作(Python)(本記事)
6. [機械操作(Arduino)](https://qiita.com/Nyanyan_Cube/items/de16e0950095d9a6244e)
7. [主要処理](https://qiita.com/Nyanyan_Cube/items/1fcb21f6ef23ae483c60)

今回は機械操作(Python)編として、```controller.py```を紹介します。

# 初期設定など
最初にシリアル通信やGPIOのセットアップをします。

```Python
ser_motor = [None, None]

GPIO.setmode(GPIO.BCM)
GPIO.setup(21,GPIO.IN)
ser_motor[0] = serial.Serial('/dev/ttyUSB0', 115200, timeout=0.01, write_timeout=0)
ser_motor[1] = serial.Serial('/dev/ttyUSB1', 115200, timeout=0.01, write_timeout=0)
```

# アクチュエータを動かすコマンドを送る
アクチュエータとはここではつまりモーターです。モーターは自作Arduino互換機に繋がれていて、Arduinoにコマンドを送ることでアクチュエータを動作させます。ここではコマンドを送る関数を紹介します。

```Python
''' アクチュエータを動かすコマンドを送る '''
''' Send commands to move actuators '''
def move_actuator(num, arg1, arg2, arg3=None):
    if arg3 == None:
        com = str(arg1) + ' ' + str(arg2)
    else:
        com = str(arg1) + ' ' + str(arg2) + ' ' + str(arg3)
    ser_motor[num].write((com + '\n').encode())
```
アクチュエータを動かすコマンドには2種類あって、これによって引数の数が違います。そこで```arg3 == None```を使って```if```文を書いています(なら関数を分ければ良いという話ではありますが)。

# パズルを掴む/離す
先程の関数を使って、パズルを掴む関数と離す関数を作りました。これは時々行う動作のため関数化してあります。

```Python
''' キューブを掴む '''
''' Grab arms '''
def grab_p():
    for i in range(2):
        for j in range(2):
            move_actuator(j, i, 1000)
        sleep(3)

''' キューブを離す '''
''' Release arms '''
def release_p():
    for i in range(2):
        for j in range(2):
            move_actuator(i, j, 2000)
```
2台のArduino```i```について、それぞれに繋がれている2つのモーター```j```を動かします。

1000や2000というのは、1000を送るとパズルを掴み、2000を送ると離すようにしてあるということです。

# アームのキャリブレーション
アームはステッピングモーターで動かしているため、適宜位置を調整してやる必要があります。Arduinoにはホールセンサ(磁気センサ)がついていて、アームには磁石をつけてあります。これを使うことで位置調整を自動で行います。Pythonからはコマンドを送るだけで位置調整ができるようになっています。

```Python
''' アームのキャリブレーション '''
''' Calibration arms '''
def calibration():
    release_p()
    sleep(0.1)
    for i in range(2):
        for j in range(2):
            move_actuator(j, i, 0, 500)
```

# 実際にロボットを解法通りに動かす
解法とその他定数を入力するとロボットを動かしてくれる関数です。

ロボットには非常停止ボタンがついていて、まずその処理が行われます。ちなみに非常停止ボタンはプルアップになっているのでコネクタが外れるだけでも動作が止まります。

そして初手以外を回す際にはパズルを持ち替え、順次モーターを回し、最大の回転数に比例した時間だけ休みます。最後に解くのにかかった時間を返します。

```Python
''' 実際にロボットを動かす '''
''' Control robot '''
def controller(slp1, slp2, rpm, ratio, solution):
    strt_solv = time()
    for i, twist in enumerate(solution):
        # 非常停止ボタンを押すと止まる
        if GPIO.input(21) == GPIO.LOW:
            if bluetoothmode:
                client_socket.send('emergency\n')
            solvingtimevar.set('emergency stop')
            print('emergency stop')
            return
        # パズルの持ち替え
        if i != 0:
            grab = twist[0][0] % 2
            for j in range(2):
                move_actuator(j, grab, 1000)
            sleep(slp1)
            for j in range(2):
                move_actuator(j, (grab + 1) % 2, 2000)
            sleep(slp2)
        max_turn = 0
        for each_twist in twist:
            move_actuator(each_twist[0] // 2, each_twist[0] % 2, each_twist[1] * 90, rpm)
            max_turn = max(max_turn, abs(each_twist[1]))
        # パズルが回転するのを待つ
        slptim = 2 * 60 / rpm * max_turn * 90 / 360 * ratio
        sleep(slptim)
    solv_time = str(int((time() - strt_solv) * 1000) / 1000).ljust(5, '0')
    return solv_time
```


# まとめ
今回は実際にロボットを動かす関数(と言ってもやっていることはコマンドを送ることですが)を解説しました。次はArduino側でコマンドを受け取ってからどうやってモーターを動かしているのかについて解説します。