toio プログラミングを iPhone / iPad で行う方法

toio は、センサーが付いた超小型のロボットプラットフォームとして手軽に利用することができます。
この記事では、iPhoneやiPadを使ってPCを使わずにプログラミングする方法を説明します。

まず、toioやPythonistaが手元にない方は次のものを用意しましょう。（合計約１万円）

- [toio コア キューブ](https://www.switch-science.com/catalog/6300/)
- [toio コア キューブ専用充電器](https://www.switch-science.com/catalog/6301/)
- [Pythonista3](https://apps.apple.com/jp/app/pythonista-3/id1085978097)

今回のサンプルプログラムは、次のファイルで構成されています。

- toio.py 実際の処理を記述しているファイル
- toio.pyui UIの定義を記述しているファイル

.pyuiについては、Pythonista上でGUIベースで定義をすることができます。


プログラムの起動直後は、toioを検索している状態の画面になります。

|![toioを検索している状態の画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21380/0936ce0c-7453-0b18-2c59-79a546a10d27.png)|
|:--|

その後、toioに接続できると次の画面になります。
この状態で、矢印を押すとその方向へ移動します。（厳密には前後のみ、右左は回転するだけ）

|![toioに接続した状態の画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21380/afaa43ee-4b94-52ae-78f6-344a69cbbe33.png)|
|:--|

動作については、[toio コア キューブ 通信仕様: モーター](https://toio.github.io/toio-spec/docs/ble_motor)に書いてある書式にそって記述します。
プログラム中の

```python
delegate.send_action('\x02\x01\x01\x32\x02\x01\x32\x0a')
```

などの部分を修正すれば様々な動きへ変更することができます。

[toio コア キューブ 通信仕様](https://toio.github.io/toio-spec/docs/ble_communication_overview)では、モーター以外の使い方についても書かれていますので、こちらを参照し、次のサンプルプログラムを改造してみてください。

```python:toio.py
import ui
import cb
import sound
import struct

TOIO_SERVICE_UUID = '10B20100-5B3B-4571-9508-CF3EFCD7BBAE'
TOIO_CHAR_UUID = '10B20102-5B3B-4571-9508-CF3EFCD7BBAE'


def button_tapped(sender):
    '@type sender: ui.Button'
    # Get the button's title for the following logic:
    t = sender.name
    if t == 'forwardButton':
        delegate.send_action('\x02\x01\x01\x32\x02\x01\x32\x0a')
    elif t == 'rightButton':
        delegate.send_action('\x02\x01\x01\x32\x02\x02\x32\x0d')
    elif t == 'leftButton':
        delegate.send_action('\x02\x01\x02\x32\x02\x01\x32\x0d')
    elif t == 'backButton':
        delegate.send_action('\x02\x01\x02\x32\x02\x02\x32\x0a')
    else:
        print('error')


class MyCentralManagerDelegate (object):
    def __init__(self):
        self.peripheral = None
        self.temp = 0

    def did_discover_peripheral(self, p):
        global text_state
        # print('+++ Discovered peripheral: %s (%s)' % (p.name, p.uuid))
        if p.name and 'toio Core Cube' in p.name and not self.peripheral:
            # Keep a reference to the peripheral, so it doesn't get garbage-collected:
            self.peripheral = p
            cb.connect_peripheral(self.peripheral)
            view['stateLabel'].text = '接続完了'

    def did_connect_peripheral(self, p):
        print('*** Connected: %s' % p.name)
        print('Discovering services...')
        p.discover_services()

    def did_fail_to_connect_peripheral(self, p, error):
        print('Failed to connect')

    def did_disconnect_peripheral(self, p, error):
        print('Disconnected, error: %s' % (error,))
        self.peripheral = None

    def did_discover_services(self, p, error):
        for s in p.services:
            if TOIO_SERVICE_UUID in s.uuid:
                print('+++ toio found')
                p.discover_characteristics(s)

    def did_discover_characteristics(self, s, error):
        if TOIO_SERVICE_UUID in s.uuid:
            for c in s.characteristics:
                if TOIO_CHAR_UUID in c.uuid:
                    self.data_characteristic = c

    def did_write_value(self, c, error):
        pass

    def did_update_value(self, c, error):
        pass

    def send_action(self, data):
        self.peripheral.write_characteristic_value(
            self.data_characteristic, data, False)


view = ui.load_view()
view.present('sheet')

delegate = MyCentralManagerDelegate()
view['stateLabel'].text = '検索中'
cb.set_central_delegate(delegate)
cb.scan_for_peripherals()

# Keep the connection alive until the 'Stop' button is pressed:
try:
    while True:
        pass
except KeyboardInterrupt:
    pass

# Disconnect everything:
cb.reset()
```

```json:toio.pyui
[
  {
    "nodes" : [
      {
        "nodes" : [

        ],
        "frame" : "{{91, 0}, {79, 93}}",
        "class" : "Button",
        "attributes" : {
          "action" : "button_tapped",
          "frame" : "{{90, 159}, {80, 32}}",
          "title" : "↑",
          "uuid" : "094421CA-AF11-4247-941A-26A72D24FCDE",
          "class" : "Button",
          "name" : "forwardButton",
          "font_size" : 15
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{0, 106}, {84, 85}}",
        "class" : "Button",
        "attributes" : {
          "action" : "button_tapped",
          "frame" : "{{90, 159}, {80, 32}}",
          "title" : "←",
          "class" : "Button",
          "uuid" : "094421CA-AF11-4247-941A-26A72D24FCDE",
          "font_size" : 15,
          "name" : "leftButton"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{178, 106}, {84, 85}}",
        "class" : "Button",
        "attributes" : {
          "action" : "button_tapped",
          "frame" : "{{90, 159}, {80, 32}}",
          "title" : "→",
          "class" : "Button",
          "uuid" : "094421CA-AF11-4247-941A-26A72D24FCDE",
          "font_size" : 15,
          "name" : "rightButton"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{91, 204}, {79, 93}}",
        "class" : "Button",
        "attributes" : {
          "action" : "button_tapped",
          "frame" : "{{90, 159}, {80, 32}}",
          "title" : "↓ ",
          "class" : "Button",
          "uuid" : "094421CA-AF11-4247-941A-26A72D24FCDE",
          "font_size" : 15,
          "name" : "backButton"
        },
        "selected" : true
      },
      {
        "nodes" : [

        ],
        "frame" : "{{91, 106}, {79, 85}}",
        "class" : "Label",
        "attributes" : {
          "name" : "stateLabel",
          "frame" : "{{56, 134}, {150, 32}}",
          "uuid" : "A42275C6-F449-4054-BF40-2A32BD61053B",
          "class" : "Label",
          "alignment" : "center",
          "text" : "",
          "font_size" : 18,
          "font_name" : "<System>"
        },
        "selected" : false
      }
    ],
    "frame" : "{{0, 0}, {262, 299}}",
    "class" : "View",
    "attributes" : {
      "background_color" : "RGBA(1.000000,1.000000,1.000000,1.000000)",
      "enabled" : true,
      "tint_color" : "RGBA(0.000000,0.478000,1.000000,1.000000)",
      "name" : "",
      "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)",
      "flex" : ""
    },
    "selected" : false
  }
]
```