UIFlowで大きめのMicroPythonスクリプトをimportする

# 概要

UIFlowはブロックを配置して簡単なプログラムを作るのに比較的便利なのですが、複雑な処理を記述するのには向いていません。

対策として、MicroPythonのスクリプトをimportして使いたくなるのですが、外部スクリプトをダウンロードしてimportする方法が用意されていません。

一方、UIFlowから使う画像などのリソースをダウンロードする機能があります。この機能は、画像ファイルしかダウンロード出来ないように見えますが、実は25[kB]以下であれば任意のファイルをダウンロードすることができます。

今回はこの機能を使ってMicroPythonスクリプトをダウンロードしてimportする方法と、25[kB]を越えるような大きなスクリプトの場合に取り得る対策について説明します。

# MicroPythonスクリプトのダウンロード

スクリプトのダウンロードは、`Resource Manager` から行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/383e2942-198b-22ac-4a80-9d3f2d29af50.png)

画面右上の `MANAGER` とツールチップが表示されるボタンを押すと、`Resource Manager` 画面が表示されます。この画面は前述のとおり画像データをダウンロードするものですが、スクリプトもダウンロードできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/ba53899a-3fd3-5c7a-e2c8-7dc6791e6b95.png)

`Add Image`ボタンを押すとファイルを選択する画面が出てきます。Windowsの場合は以下のようなダイアログが表示され、フィルタとして `*.bmp;*.dib;*.jfif;*.pjpeg;*.jpeg;*.pjp;*.jpg` が指定されているため、そのままではMicroPythonのスクリプト (*.py)が表示されません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/31f29e2b-e28c-3e3f-566c-575d44b93f48.png)

他のWindowsのアプリケーションと同様、フィルタを`すべてのファイル` にすることにより、拡張子に関係なくファイルが表示されますので、MicroPythonのスクリプトも選択できるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/ecab2ff0-9ed1-eb74-e8a1-9abe6acc2af0.png)

注意点として、M5Stack/M5StickCのフラッシュROMで使っているファイルシステムの制約上、ファイル名は10文字以下の必要があります。

ここまでは、単にWindows上でのファイル操作入門みたいな話なので特に面白いことはありません。

以降、実験用に `testmod.py`という名前で以下の内容のスクリプトをダウンロードしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/e220fc75-0d3f-21ba-c5d1-c17cdb8d0bba.png)

```testmod.py
hoge = 'hoge'
def hello() -> None:
    print('hello ' + hoge)
```

# ダウンロードしたスクリプトのimport

ダウンロードしたスクリプトは、ターゲットデバイス(M5Stack/M5StickC)のフラッシュROMに保存されます。
ただし、このままだと`import`文でインポートするときの検索パスではない場所に保存されているので、インポートに失敗します。

試しに、TeraTermなどのシリアルコンソールからREPLを操作してインポートしてみます。(UIFlowのファームウェアが動作しているので、`Ctrl+C`を2～3回押してREPLの入力受付状態にします。)

```
M5Cloud connected.
m5cloud thread begin .....
mqtt reconnect
mqtt reconnect ok // <- この状態でCtrl+Cを3回入力
Unhandled exception in thread started by <bound_method>
Traceback (most recent call last):
  File "flowlib/lib/time_ex.py", line 62, in timeCb
KeyboardInterrupt:
Unhandled exception in thread started by <bound_method>
Traceback (most recent call last):
  File "flowlib/m5cloud.py", line 178, in _daemonTask
  File "flowlib/lib/time_ex.py", line 62, in timeCb
KeyboardInterrupt:
Traceback (most recent call last):
  File "flow.py", line 42, in <module>
  File "flowlib/m5cloud.py", line 215, in run
  File "flowlib/m5cloud.py", line 190, in _backend
  File "flowlib/m5cloud.py", line 178, in _daemonTask
  File "flowlib/lib/time_ex.py", line 62, in timeCb
KeyboardInterrupt:
MicroPython v1.11-294-gf32fecff7-dirty on 2019-08-30; ESP32 module with ESP32
Type "help()" for more information.
>>>
```

```
>>> import testmod
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: no module named 'testmod'
>>>
```

`testmod` というモジュールは無いと言われてしまいました。

MicroPythonでは、モジュールは`sys.path`のリストに含まれるパスから検索されます。UIFlowのファームウェアでは、以下のとおり `['', '/lib', 'flowlib', 'flowlib/lib']` となっています。

```
>>> import sys
>>> sys.path
['', '/lib', 'flowlib', 'flowlib/lib']
>>>
```

一方、ダウンロードしたスクリプトは、`/flash/res` に保存されています。

```
>>> import os
>>> os.listdir('/flash/res')
['default.jpg', 'error.jpg', 'wisun.mpy', 'testmod.py']
>>>
```

よって、`/flash/res` を `sys.path` に追加すると、UIFlowのResource Managerでダウンロードしたスクリプトをインポートできるようになります。

```
>>> sys.path.append('/flash/res')
>>> import testmod
>>> testmod.hello()
hello hoge
>>> testmod.hoge
'hoge'
>>>
```

UIFlowで同じことを行うには、以下のようにブロックをつなぎます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/bf4820bf-74dd-b684-61b6-b2cc264ae0eb.png)

# 大きいスクリプトのダウンロード

前述の通り、UIFlowのResource Managerでダウンロードできるファイルサイズは25[kB]に制限されています。
また、この制限を越えない場合でも、あまり大きいスクリプトをインポートしようとすると、memory allocation errorが発生する場合があります。

memory allocation errorが発生するのは、インポート時にターゲットデバイス上でスクリプトをバイトコードにコンパイルするときに、スクリプトが大きすぎるためメモリ不足になるためです。

これらの対策としては、PC上で事前にバイトコードにコンパイルしておくという方法があります。

バイトコードコンパイルを行うために、公式のMicroPythonには `mpy-cross` というツールが用意されています。MicroPythonをビルドすると、`mpy-cross/mpy-cross` 以下に実行可能ファイルが生成されます。

このツールにMicroPythonスクリプト (*.py)を指定して実行すると、同じディレクトリに元のスクリプトと同じファイル名で拡張子が `*.mpy` のファイルが出力されます。この *.mpy ファイルを Resource Manager経由でダウンロードすれば、*.pyと同様にインポートできるようになります。

今のところ、M5StickC用のWi-SUN HAT制御用のモジュールが25[kB]を越えており700行超あるため、Resource Managerの制約に引っかかる上、ほかの方法でダウンロードしてインポートしてもmemory allocation errorで失敗します。
このスクリプトをmpyに変換したところ、Resource Managerでダウンロードしして正常にimportできるようになっています。

[動かしているスクリプト](https://gist.github.com/ciniml/677451bf20ca78bf98000dd28fc10051)

興味のある方は、前に書いた [MicroPythonのビルド記事](https://qiita.com/ciniml/items/1378d02bc14098b959ef) を見ていただければ、お試しいただけると思います。


現時点では MicroPython をビルドしたうえで `mpy-cross` を使うという手順が必要なためハードルが高いと思われますので、何かしらツールを用意しようかなと考えています。

アップロードしたスクリプトをコンパイルしてダウンロードするサイトを用意しましたのでお試しください。 [https://micropythonbytecodeconverter.azurewebsites.net/](https://micropythonbytecodeconverter.azurewebsites.net/)