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
ボタンを押して、ブロックのパラメータを追加します。
パラメータはname
とtype
の属性を持っています。
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}
と書くことにより、name
がparam
のパラメータの内容を参照できます。
パラメータの内容が そのまま 展開されるので、文字列として扱いたい場合は、"${param}"
のようにダブル・クォーテーションで括ります。
TypeがExecuteのstart
ブロックのコードを以下に示します。
コード中の ${rx}
や${tx}
がパラメータを埋め込んでいる箇所です。
${log_debug}
や${use_i2c}
はStringパラメータですが、わざとダブル・クォーテーションで囲まずに参照しています。Blockly上でlog_debugやuse_i2cにTrue
やFalse
を直接記述して使うことを想定しています。
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コードはあまり長くすると、スクリプト実行前にメモリ不足のエラーになったり、保存先の領域が不足して動かなくなります。
この問題に対処するには、 前に書いた記事 のとおり、
- スクリプトを事前にバイトコード・コンパイルしておく
- バイトコード・コンパイルしたスクリプトを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 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
を制御して、家庭のスマートメーターと通信して、現在の消費電力や、積算の消費電力量を取得することが出来ます。
ブロックの色がオレンジ色なのは、 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
変数の値を取得します。
settings1 = 'value'
route_b_id = 'ルートB ID'
route_b_password = 'ルートB パスワード'
ソースコードはこちら
使用例
Wi-SUNモジュールのプログラム中で、ルートB IDやパスワードを管理するのに使っています。
ソースコードはこちら
まとめ
とりあえずここまでの説明で、UI Flowのカスタム・ブロックが作れるかなとは思います。
今回紹介したブロックで使っているソースコードやブロックの定義は、筆者のGitHubリポジトリに置いてありますので、興味がありましたらご確認ください。
とりあえず現在のところ、 @rin_ofumi さんのCO2 HATを使うためのカスタム・ブロックを作っているところです。多分Wi-SUNブロックと同じような構造になると思います。