自作モジュール用の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つの項目が表示されます。

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

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

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

グループ名の設定

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

ブロックの色の設定

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

ブロックの追加/選択

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

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

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

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

Valueブロックは、関数呼び出しのように実行した結果の値をもつブロックです。例えばセンサーの値を読み取るブロックなどが該当します。
ブロックの形状が のように左側に別のブロックをつなげられる形になります。

逆に結果の値をもたない場合はExecuteブロックにします。 のように、前後の処理とつなげられる形になります。

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

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

パラメータはnametypeの属性を持っています。

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

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

Labelパラメータ

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

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

Variableパラメータ

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

Numberパラメータ

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

Stringパラメータ

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

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

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

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

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

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

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のコードが式であることが必要です。

wisun_instance.values()

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

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

カスタム・ブロックを使う

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

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

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

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

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

この問題に対処するには、 前に書いた記事 のとおり、

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

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

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

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

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

バイトコード・コンパイルは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を制御して、家庭のスマートメーターと通信して、現在の消費電力や、積算の消費電力量を取得することが出来ます。

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

ソースコードはこちら

使用例

ブロックにラベルを付けるのを忘れたのでわかりにくいのですが、一番最初に呼び出しているのが 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 ブロックが含まれます。

以下のような内容で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 パスワード'

ソースコードはこちら

使用例

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

ソースコードはこちら

まとめ

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

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

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