iPhoneを使ってM5BALAを操作する（Pythonista-MQTT編）

#概要

前の投稿やった [iPhoneを使ってM5BALAを操作する（JavaScript-MQTT編）](https://qiita.com/inasawa/items/a322fb3e8b106f6ff912) の Pythonista版です。

- iPhone の傾きを Pythonista で検知する
- Pythonista から　MQTT 経由で制御コマンドを送信する
- 受信した制御コマンドに従ってM5BALA+M5GOが動く

Pythonista は iPhone や iPad 上で Python が実行できる開発環境。以前 Pythonista を知った時は、興味は持ったものの、有料(1200円)であることや、iPhone や iPad 上でコーティングなんて外付キーボードを使っても無理、と思って使わずでしたが、今は iCloud Drive で連携できるので、Mac で iCloud Drive 上のファイルを編集し、iPhone側で iCloud Drive 上のファイルをそのまま実行できるのでとっても楽です。BLEが使えるので、M5Stack と BLE で連携というのも、そのうち試したいことです。

##環境

- M5BALA+M5GO
- M5UI.Flow (Firmware 0.7.2/0.8.0 で確認)
- iMac (macOS Mojave)
- MQTT Broker (mosquitto version 1.4.15)
- MQTT Client (paho-mqtt 1.4.0)
- [Pythonista3 v3.2](http://omz-software.com/pythonista/)
- iPhone6 iOS 12.0.1

#実行例

iPhoneを前後左右に傾けることで、M5BALAが前進・後進・右回転・左回転します。

![IMG_1792.jpeg](https://qiita-image-store.s3.amazonaws.com/0/133064/614b845e-b2b9-07c0-42b1-b367d0c3863d.jpeg)

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">前回JavaScriptでやったことをPythonistaにしました。私のM5GOは今ジャイロが使えないので、バランスモードは使わず牽引車にしています。左手はiPadでビデオ、右手はIPhoneで操作、難しい〜。<br>「iPhoneを使ってM5BALAを操作する（Pythonista-MQTT編）」<a href="https://t.co/TdyAiuA1yW">https://t.co/TdyAiuA1yW</a><a href="https://twitter.com/hashtag/M5Stack?src=hash&amp;ref_src=twsrc%5Etfw">#M5Stack</a> <a href="https://twitter.com/hashtag/M5BALA?src=hash&amp;ref_src=twsrc%5Etfw">#M5BALA</a> <a href="https://t.co/ivwNKtbUga">pic.twitter.com/ivwNKtbUga</a></p>&mdash; 稲澤祐一 (@inasawa) <a href="https://twitter.com/inasawa/status/1055696103880839168?ref_src=twsrc%5Etfw">2018年10月26日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">M5BALAが引っ張る荷台にiPhoneを乗っけて撮影してみました。テーブル＆椅子の脚の間を走らせています。<a href="https://twitter.com/hashtag/M5Stack?src=hash&amp;ref_src=twsrc%5Etfw">#M5Stack</a> <a href="https://twitter.com/hashtag/M5BALA?src=hash&amp;ref_src=twsrc%5Etfw">#M5BALA</a> <a href="https://t.co/AhU5CIBIgm">pic.twitter.com/AhU5CIBIgm</a></p>&mdash; 稲澤祐一 (@inasawa) <a href="https://twitter.com/inasawa/status/1056072532648837120?ref_src=twsrc%5Etfw">2018年10月27日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

#Pythonista3プログラム

iPhoneの前後左右の傾きに応じて、左右の車輪を動かすための指示をMQTT経由で送信する仕組みです。

MQTTクライアントが必要なので [StaSh - Shell Like an Expert in Pythonista](https://forum.omz-software.com/topic/1919/stash-shell-like-an-expert-in-pythonista) をインストール。（Pythonista のコンソールで以下を実行）

```
import requests as r; exec(r.get('http://bit.ly/get-stash').text)
```

StaSh を実行し pip で paho-mqtt をインストール。

```shell:
pip install paho-mqtt
```

```python:m5bala_controller.py
import motion
import time
import paho.mqtt.client as mqtt
import math
import ui

MQTT_BROKER = 'MQTTBroker' # MQTTBroker のアドレスを設定
MQTT_TOPIC  = 'M5BALA'

def end_controller(sender):
    global action_loop
    sender.superview.close()
    send_action('I')
    action_loop = False

def send_action(action):
    global last_action
    client.publish(MQTT_TOPIC, action)
    view['action'].text = action
    last_action = action

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

client = mqtt.Client('iPhonePythonista3', protocol=mqtt.MQTTv311)
client.connect(MQTT_BROKER, keepalive=60)

motion.start_updates()

# 'I' : Init Params
send_action('I')

action_loop = True
while action_loop:
    roll, pitch, yaw = motion.get_attitude()
    yaw   = math.degrees(yaw)
    pitch = math.degrees(pitch)
    roll  = math.degrees(roll)

    view['yaw'].text   = '{:.2f}'.format(yaw)
    view['pitch'].text = '{:.2f}'.format(pitch)
    view['roll'].text  = '{:.2f}'.format(roll)

    move = max(-60, min(60, pitch)) * 4
    turn = max(-60, min(60, roll))  * 1
    action = 'L={:.2f},R={:.2f}'.format(move + turn / 2, move - turn / 2)
    if action != last_action:
        send_action(action)
    time.sleep(0.5)
```

###Pythonista3 UI

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

        ],
        "frame" : "{{17, 197}, {337, 45}}",
        "class" : "Label",
        "attributes" : {
          "name" : "action",
          "frame" : "{{45, 104}, {150, 32}}",
          "uuid" : "95E5B18C-90A9-4784-BA75-4B1016070924",
          "class" : "Label",
          "alignment" : "center",
          "text" : "Action",
          "font_size" : 36,
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{96, 27}, {60, 32}}",
        "class" : "Label",
        "attributes" : {
          "name" : "label2",
          "frame" : "{{82, 125}, {150, 32}}",
          "uuid" : "5144134F-E68A-4023-A81E-A67824AF01CA",
          "class" : "Label",
          "alignment" : "right",
          "text" : "方角",
          "font_size" : 24,
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{96, 67}, {60, 32}}",
        "class" : "Label",
        "attributes" : {
          "name" : "label3",
          "frame" : "{{82, 125}, {150, 32}}",
          "uuid" : "01A7842C-1680-4655-9F09-F3B52A0721B6",
          "class" : "Label",
          "alignment" : "right",
          "text" : "前後",
          "font_size" : 24,
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{96, 107}, {60, 32}}",
        "class" : "Label",
        "attributes" : {
          "name" : "label4",
          "frame" : "{{82, 125}, {150, 32}}",
          "uuid" : "A5EF17D2-3351-4AA3-B918-A82382B2DF4D",
          "class" : "Label",
          "alignment" : "right",
          "text" : "左右",
          "font_size" : 24,
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{164, 27}, {103, 32}}",
        "class" : "Label",
        "attributes" : {
          "name" : "yaw",
          "frame" : "{{82, 125}, {150, 32}}",
          "uuid" : "F6954500-8FAB-4E42-B4A6-29A7865C50F9",
          "class" : "Label",
          "alignment" : "right",
          "text" : "",
          "font_size" : 24,
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{164, 67}, {103, 32}}",
        "class" : "Label",
        "attributes" : {
          "name" : "pitch",
          "frame" : "{{82, 125}, {150, 32}}",
          "uuid" : "A73FDD85-DBEE-4857-9B9E-39B13FEEA553",
          "class" : "Label",
          "alignment" : "right",
          "text" : "",
          "font_size" : 24,
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{164, 107}, {103, 32}}",
        "class" : "Label",
        "attributes" : {
          "name" : "roll",
          "frame" : "{{82, 125}, {150, 32}}",
          "uuid" : "7744E0CF-F2CD-4985-AAD3-A68158E7F67D",
          "class" : "Label",
          "alignment" : "right",
          "text" : "",
          "font_size" : 24,
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{136, 266}, {87, 39}}",
        "class" : "Button",
        "attributes" : {
          "action" : "end_controller",
          "border_width" : 1,
          "frame" : "{{146, 149}, {80, 32}}",
          "title" : "終了",
          "uuid" : "0EC4234B-2C78-4812-89A4-236FB5384680",
          "class" : "Button",
          "corner_radius" : 8,
          "font_bold" : true,
          "name" : "button1",
          "font_size" : 24
        },
        "selected" : false
      }
    ],
    "frame" : "{{0, 0}, {372, 330}}",
    "class" : "View",
    "attributes" : {
      "name" : "M5BALA Controller",
      "enabled" : true,
      "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)",
      "background_color" : "RGBA(1.000000,1.000000,1.000000,1.000000)",
      "tint_color" : "RGBA(0.000000,0.478000,1.000000,1.000000)",
      "flex" : ""
    },
    "selected" : false
  }
]
```