PS3コントローラーでプログラミング・フォロを操作する

#やること
ラズパイに繋いだPS3コントローラーで、Bluetoothで接続したmicro:bit搭載のプログラミング・フォロを操作できるようにしてみた。

##プログラミング・フォロ
まずはプログラミング・フォロの組み立てについて。
[組み立てはこちら](https://qiita.com/roxa/items/46d701568fb745b8024b)

##ラズパイからmicro:bitにBluetoothで繋ぐ
次にラズパイとmicro:bitをBluetoothで接続する方法。
micro:bitのプログラムもこれを使う。
[方法はこちら](https://qiita.com/roxa/items/b3f3061de17d97f7271a)

##ラズパイにPS3コントローラーを繋ぐ
次にラズパイにPS3のコントローラーを繋ぐ方法。
[方法はこちら](https://qiita.com/roxa/items/33c464960c932ba497fa)

##PS3コントローラーの操作でプログラミング・フォロを操作する
今までのを合わせて、PS3コントローラーを操作してプログラミング・フォロを操作出来るようにしてみました。
[プログラムはgithubにも](https://github.com/roxa-delphi/microbit/blob/master/folo_control.py)

```python:folo_control.py
from bluepy import btle
import binascii
import struct
import time

devadr           = "e4:1e:1f:1f:f4:49"
uuid_svc_iopin   = "e95d127b-251d-470a-a062-fa1922dfa9a8"
uuid_pin_io_conf = "e95db9fe-251d-470a-a062-fa1922dfa9a8"
uuid_pin_ad_conf = "e95d5899-251d-470a-a062-fa1922dfa9a8"
uuid_pin_data    = "e95d8d00-251d-470a-a062-fa1922dfa9a8"

device_path  = "/dev/input/js0"
EVENT_FORMAT = "LhBB"
EVENT_SIZE   = struct.calcsize(EVENT_FORMAT)

def main(chPin):

  with open(device_path, "rb") as device :
    event = device.read(EVENT_SIZE)

    while event :
      (ds3_time, ds3_val, ds3_type, ds3_num) = struct.unpack(EVENT_FORMAT, event)
      #print("{0}, {1}, {2}, {3}".format(ds3_time, ds3_val, ds3_type, ds3_num))

      # X button to end
      if ds3_num == 0 and ds3_type == 1:
        #print(" move stop")
        chPin.write(b"\x0d\x00")
        chPin.write(b"\x0e\x00")
        #print(" rotate stop")
        chPin.write(b"\x0f\x00")
        chPin.write(b"\x10\x00")
        per.disconnect()
        return

      # right stick to rotate
      if ds3_num == 3 and ds3_type == 2:

        if abs(ds3_val) < 4000:
          #print(" rotate stop")
          chPin.write(b"\x0f\x00")
          chPin.write(b"\x10\x00")
        elif ds3_val > 0 :
          val = ds3_val>>7
          #print(" rotate right %d" %val)
          chPin.write(b"\x0f\x00")
          chPin.write(b"\x10" + val.to_bytes(1, 'little'))
        else :
          ds3_val = -ds3_val
          val = ds3_val>>7
          #print(" rotate left  %d" %val)
          chPin.write(b"\x10\x00")
          chPin.write(b"\x0f" + val.to_bytes(1, 'little'))

      # left stick to move
      if ds3_num == 1 and ds3_type == 2:

        if abs(ds3_val) < 4000:
          #print(" move stop")
          chPin.write(b"\x0d\x00")
          chPin.write(b"\x0e\x00")
        elif ds3_val > 0 :
          val = ds3_val>>7
          #print(" move back    %d" %val)
          chPin.write(b"\x0d\x00")
          chPin.write(b"\x0e" + val.to_bytes(1, 'little'))
        else :
          ds3_val = -ds3_val
          val = ds3_val>>7
          #print(" move forward %d" %val)
          chPin.write(b"\x0e\x00")
          chPin.write(b"\x0d" + val.to_bytes(1, 'little'))

      time.sleep(0.01)
      event = device.read(EVENT_SIZE)

if __name__ == "__main__" :
  per = btle.Peripheral(devadr, btle.ADDR_TYPE_RANDOM)

  svcPinIO = per.getServiceByUUID(uuid_svc_iopin)

  #pin13-16 as Analog
  chPinAD = svcPinIO.getCharacteristics(uuid_pin_ad_conf)[0]
  chPinAD.write(b"\x00\xe0\x01\x00")

  #pin13-16 as Output
  chPinIO = svcPinIO.getCharacteristics(uuid_pin_io_conf)[0]
  chPinIO.write(b"\x00\x00\x00\x00")

  #pin
  chPin = svcPinIO.getCharacteristics(uuid_pin_data)[0]

  print("Remote control Folo by PS3 controller")
  print("  left  stick : move to forward or back")
  print("  right stick : turn left or right")
  print("  Button X    : End")

  main(chPin)

```
簡単に言うと、
micro:bitのIO端子をアナログの出力が出来るようにして、
PS3のスティックの信号(-32767〜0が前進、0〜32767が後退)を見て、動かすモーターの端子(前進ならP13)に信号を出力する(0〜255の範囲)。
入力信号から出力信号へは数値の範囲を合わせるためにそのまま1/128(右へ7bitシフト)している。

##実行
```
% python3 folo_control.py
```
操作は下記に割り振ってある

|操作          |動作     |
|-------------+---------|
|左スティック上下|前進、後退|
|右スティック左右|左右回転  |
|Xボタン　　　　 |終了     |
![ps3_folo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/576327/811b8135-1b5f-6448-6eb4-d7cac76c932e.gif)
前進、後退、左右への回転もちゃんと動くし、スティックの倒す量にしたがってスピードも変わるように動いた。