自作モジュール用のUI Flowのカスタムブロックを作る

# UI Flowとは

UI FlowはM5Stack公式のM5Stackシリーズ用プログラミング環境です。

特徴としては、Blocklyと呼ばれるブロック・プログラミング環境を使ってプログラムを記述し、対応するMicroPythonコードをインターネット経由、もしくはローカルのシリアル通信経由でデバイスにダウンロードすることができます。

ブロック・プログラミング環境を使わない場合、MicroPythonのコードを直接記述することもできます。


# 自作M5Stackモジュールを使う場合の課題

M5Stack社公式のモジュールの場合、UI Flowのファームウェアに制御用のライブラリが含まれており、専用のブロックが用意されているため、モジュールを使うプログラムをBlocklyで比較的簡潔に記述することができます。

一方、自作M5Stackシリーズ用ハードウェアを、UI Flowから使おうとする場合、専用のブロックが無いため、
Blocklyで用意されているブロックを組み合わせて自作モジュールの制御処理を記述する必要があります。

この場合、モジュールの内容にもよりますが、大体の場合はBlocklyで記述するのには向かない複雑な処理が必要となるため、Blockly単体で記述するのは現実的ではありません。

対策として、UI FlowにはユーザーがBlocklyのブロックを作成して使用するための `カスタム・ブロック作成機能` があります。

# UIFlow Block Maker

UI FlowのBlockly編集画面のグループ選択リストの一番下にある`Custom (Beta)` を選択すると、`Create *.m5b file` `Open *.m5b file` `User Manual` の3つの項目が表示されます。

![uiflow_custom_block_maker.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/60393ec3-a155-f6b5-6416-8486b40aa548.png)


ここで `Create *.m5b file` を選択すると、UIFlow Block Makerが別のタブで開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/6f16ff8f-929b-2873-de38-227b5be75f80.png)

この画面でカスタム･ブロックの内容を記述していきます。以下はWi-SUNモジュール用のカスタム･ブロックを開いた状態の画面です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/914fb081-aa0a-0edf-7047-24b4785a6102.png)

# UIFlow Block Maker でカスタム･ブロックを作る

## グループ名の設定

まずは、カスタム･ブロックのグループ名を決めて`Namespace`に入力します。`Namespace`の値は、カスタム･ブロックをUI Flowに読み込んだときに属するグループの名前になります。
Wi-SUN用カスタム･ブロックではグループ名を`Wi-SUN`にしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/831a0790-e6d9-1a24-5ff2-6fa15e419e17.png)

## ブロックの色の設定

`Block Color` のボタンを押すと、色選択画面が表示されます。ここで設定した色がBlockly上で表示されるカスタム･ブロックの色となります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/dccfc0e9-0365-75cb-71ee-c03305eff046.png)

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/f8eed2f2-bd05-545f-b01e-f15880321eec.png" width="33%"/>


## ブロックの追加/選択

画面下の `Add Block` ボタンを押してカスタム･ブロックを追加するか、編集したいカスタム･ブロックを `Added` リストから選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/ba1b78c1-c262-76df-14d1-1814a524891b.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/81bbcaef-4738-e91d-8fe2-b443669bc570.png)

## ブロックの名前と種類の設定

`Name`にカスタム･ブロックの名前を入力します。この名前がUI FlowのBlockly画面のブロック一覧に **表示され…ません。** あくまで定義上の名前です。

`Type`でブロックの種類を`Value`もしくは`Execute`から選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/0a1e4c39-6894-d794-a7f1-d9e6804c7b51.png)

Valueブロックは、関数呼び出しのように実行した結果の値をもつブロックです。例えばセンサーの値を読み取るブロックなどが該当します。
ブロックの形状が <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/371a9e3c-a954-3c84-5c18-1fd53e3fbe41.png"></img> のように左側に別のブロックをつなげられる形になります。

逆に結果の値をもたない場合はExecuteブロックにします。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/0544e7ff-5e72-5048-d23f-aaeaaa6c75e0.png) のように、前後の処理とつなげられる形になります。

## ブロックのパラメータの追加/編集

`Parameter` グループの下部にある `Add` ボタンを押して、ブロックのパラメータを追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/f1337a68-4b68-7d0f-f879-70af6d4189b8.png)

パラメータは`name`と`type`の属性を持っています。

`name`は画面上に表示されるパラメータの名前として使われます。また、ブロックに対応するMicroPythonコード内でパラメータを参照するときにも用います。

`type`はパラメータの種類を指定します。現在のところ、`Label`, `Variable`, `Number`, `String` の4つのパラメータの種類があります。

### Labelパラメータ
ブロックの画面上に任意の文字列を表示するために使用します。最初のパラメータはLabelパラメータとして、ブロックの名前を入れておくと良いです。
先ほどのwaitブロックではただ一つnameが"Wait WiSUN Update"でtypeがLabelのパラメータを持っています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/0544e7ff-5e72-5048-d23f-aaeaaa6c75e0.png) 

ちなみに、Wi-SUNのstartブロックではそのことに気付く前に他のパラメータを入力してしまったため、ブロックの名前が表示されていません。現状、パラメータの並び替えはサポートされていないので、気をつけましょう。(厳密にはブロック定義のJSONを直接編集すれば変えることは可能ですが面倒です)

### Variableパラメータ
他のブロックの値を入力するパラメータを定義します。このパラメータに対応する行は右側が欠けた状態となり、他のValueブロックを接続してその結果をパラメータの値とすることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/ac869997-e948-b4f0-ac30-f2665cf15555.png)

### Numberパラメータ
数値を入力できるパラメータを定義します。現状デフォルト値は設定できないようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/35d0c4ea-b2e1-8852-cd85-f133e9eccbc1.png)

### Stringパラメータ
任意の文字列を入力できるパラメータを定義します。Numberと同様デフォルト値の設定は出来ないようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/1606977c-eb54-42ca-aa45-400a9126f90a.png)


## ブロックの処理内容の設定

ブロックの処理内容を`Block Code`にMicroPythonで記述します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/45294049-1ca1-60b3-d58a-e7fdff2acc92.png)

コード上では `${param}` と書くことにより、`name`が`param`のパラメータの内容を参照できます。

パラメータの内容が **そのまま** 展開されるので、文字列として扱いたい場合は、`"${param}"`のようにダブル・クォーテーションで括ります。

TypeがExecuteの`start`ブロックのコードを以下に示します。
コード中の `${rx}`や`${tx}`がパラメータを埋め込んでいる箇所です。
`${log_debug}`や`${use_i2c}`はStringパラメータですが、わざとダブル･クォーテーションで囲まずに参照しています。Blockly上でlog_debugやuse_i2cに`True`や`False`を直接記述して使うことを想定しています。

```python
def wisun_start():
    import sys
    import machine
    sys.path.append('/flash/res')
    import wisun
    wisun_uart = machine.UART(1, rx=${rx}, tx=${tx}, baudrate=115200)
    if ${log_debug}:
       import logging
       logging.basicConfig(logging.DEBUG)
    if ${use_i2c}:
        import i2c_bus
        i2c = i2c_bus.get(i2c_bus.M_BUS)
        ioe = wisun.IOExpander(i2c=i2c, address=24, output=0x03, inversion=0x00, direction=0xfc)
        # Initialize BP35A1 interfaces
        wisun_wkup = ioe.pin(0)
        wisun_reset = ioe.pin(1)
    else:
        wisun_wkup = None
        wisun_reset = machine.Pin(${reset}, machine.Pin.OUT)
    global wisun_instance
    wisun_instance = wisun.WiSUN(wisun_uart, wisun_wkup, wisun_reset)
    wisun_instance.start(${route_b_id}, ${route_b_password})

wisun_start()
```

TypeがValueの`values`ブロックのコードを以下に示します。
Valueの場合、記述したコードが、値を受け取るブロックのコードに埋め込まれるようになりますので、Pythonのコードが式であることが必要です。

```python
wisun_instance.values()
```

## カスタム・ブロックの保存

カスタム･ブロックの定義が終わったら、画面右下の`Download`ボタンを押して、カスタム・ブロックの定義をファイルに保存します。
`(namespaceの値).m5b`という名前のファイルがダウンロードされます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/df8cb56b-82a6-c9d4-c9ff-0c471aa23152.png)


# カスタム･ブロックを使う

作ったカスタム･ブロックを使うには、UI FlowのCustom (Beta)グループから、`Open *.m5b file` を選んで、保存したカスタム･ブロックのファイルを開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/ee2aa141-2dee-0ee3-9987-a2a177a93057.png)

成功すると、Custom (Beta)グループの下に、namespaceで指定した名前のグループが追加されます。

グループを選ぶと、作成したカスタム･ブロックが表示されますので、あとは標準のブロックと同じようにブロックを組み立ててプログラムを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/82360cec-297b-8070-097c-69df3f7ad01d.png)

# 複雑な処理を行うカスタム･ブロックを作る

UIFlow Block Makerで、ブロックに対応するMicroPythonコードはあまり長くすると、スクリプト実行前にメモリ不足のエラーになったり、保存先の領域が不足して動かなくなります。

この問題に対処するには、 [前に書いた記事](https://qiita.com/ciniml/items/68bc7f362d6985efc640) のとおり、

1. スクリプトを事前にバイトコード・コンパイルしておく
2. バイトコード・コンパイルしたスクリプトをResource Managerから対象のM5Stack/StickCにダウンロードする

という方法で対処可能です。

実際、Wi-SUNの通信処理はそこそこ複雑で700行くらいあるのですが、このままだとUI Flowで使えないので、Wi-SUNカスタム･ブロックではダウンロード済みのスクリプトをimportして使うようになっています。(wisun_start関数の冒頭部分)

```python
def wisun_start():
    import sys
    import machine
    sys.path.append('/flash/res')
    import wisun
    ...
```

`sysモジュール`の`path属性`にResource Managerでダウンロードしたファイルが置かれる`/flash/res`ディレクトリを追加して、Resource Managerでダウンロードしたスクリプトをimportできるようにしています。その後、`import wisun`で`wisunモジュール`をインポートしています。

バイトコード･コンパイルはMicroPythonをビルドするとできあがる`mpy-cross`ツールが必要ですが、ビルドするの面倒な人向けに、アップロードしたスクリプトをバイトコードコンパイルするWebアプリを用意しといたので、よろしければどうぞ。 https://micropythonbytecodeconverter.azurewebsites.net/

# 作ったカスタム･ブロックの紹介

以降、作ったカスタム･ブロックを紹介します。

## Wi-SUN Stack / Wi-SUN HAT用ブロック

### 概要

先ほどから説明用に何度か出てきているカスタム･ブロックが、
拙作M5Stack Wi-SUNモジュール、および @rin_ofumi さんがBOOTHで販売されている、M5StickC Wi-SUN HATを制御するためのカスタム･ブロックです。

ローム製Wi-SUNモジュール`BP35A1`を制御して、家庭のスマートメーターと通信して、現在の消費電力や、積算の消費電力量を取得することが出来ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/c4b3da75-418c-6d6a-a793-2543f5aa269d.png)

ブロックの色がオレンジ色なのは、 M5StickC Wi-SUN HATのケースがオレンジ色なのが印象に残っていたからのような気がします。たぶん。

ソースコードは[こちら](https://github.com/ciniml/M5_UIFlow_Blocks/tree/master/blocks/wisun)

### 使用例

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/1b6ce622-413e-dbce-ab25-8f9da5ce0f65.png" width="33%"/>

ブロックにラベルを付けるのを忘れたのでわかりにくいのですが、一番最初に呼び出しているのが `start` ブロックです。

Wi-SUNでスマートメーターと通信するのに必要な `ルートB ID`と`ルートB パスワード`、BP35A1が接続されているピンの番号、I2C接続IOエキスパンダを使うかどうかを指定して、専用の処理タスクで通信処理を開始します。

その後、メイン･ループ中で、`Get WiSUN Values (Blocked)`ブロックから値を取得するようにします。
このブロックは、`start`ブロックで開始したWi-SUN処理タスクの処理の結果、通信状態が変化したり、新しい電力情報が取得できるまで処理を中断し、状態を表す値を返します。

メイン･ループ中では、取得した接続状態や電力量を画面に表示したり、データ収集サービスであるAmbientに送信したりしています。

## MyConfigブロック

### 概要

パスワードなど、Blocklyに記述したくないような設定を別のMicroPythonスクリプトで定義して読み込むモジュールです。

設定値を含む `mycfg` モジュールをインポートする `Load MyConfig` ブロックと、 mycfgモジュールに含まれる値を取得する `MyConfig value` ブロックが含まれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/72503307-0d5b-196e-1a54-b3d51d2e9da3.png)

以下のような内容で`mycfg.py`を作成してResource Managerを使ってデバイスにダウンロードしておくと、`MyConfig value`ブロックを使って値を取得することが出来ます。
例えば、MyConfig valueブロックの`name`パラメータに`route_b_id`を指定すると、mycfg.pyの`route_b_id`変数の値を取得します。

```mycfg.py
settings1 = 'value'
route_b_id = 'ルートB ID'
route_b_password = 'ルートB パスワード'
```

ソースコードは[こちら](https://github.com/ciniml/M5_UIFlow_Blocks/tree/master/blocks/mycfg)

### 使用例

Wi-SUNモジュールのプログラム中で、ルートB IDやパスワードを管理するのに使っています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58388/9e9660a5-a9df-324d-5075-6d5fd1e28883.png" width="33%"/>

ソースコードは[こちら](https://github.com/ciniml/M5_UIFlow_Blocks/tree/master/examples/wisun)

# まとめ

とりあえずここまでの説明で、UI Flowのカスタム･ブロックが作れるかなとは思います。

今回紹介したブロックで使っているソースコードやブロックの定義は、筆者のGitHubリポジトリに置いてありますので、興味がありましたらご確認ください。

https://github.com/ciniml/M5_UIFlow_Blocks/tree/master/examples/wisun

とりあえず現在のところ、 @rin_ofumi さんのCO2 HATを使うためのカスタム･ブロックを作っているところです。多分Wi-SUNブロックと同じような構造になると思います。