検証環境
本体:
M5StackC
ファームウェアバージョン:
UIFlow-v1.3.2
USBモードに変更したい
こちらのページを参考にしてください。
USBポートに刺しても認識しない
Windows Update へのアクセスを制限されている環境(WSUS サーバ以外アクセス禁止等)で、公式サイトからダウンロードできる「CP210X Driver」では、M5StickC を刺しても COM ポートを認識してくれません。
「FTDI VCP Driver」をインストールしてください。
USBモードでのポート名を知りたい。
Windows PC の場合は ComX
、WSL環境では /dev/ttySX
(X≒ComXの番号と同一)です。
※その他の環境は試してないので分かりません。
NTPサーバと時刻同期するには
UIFlow-v1.3.2
Firmware には時刻は設定できますが、NTPサーバと同期(ntp_sync)するモジュールが入っていません。
こちらのページで公開されている ntptime.py
を使わせていただくと同期できるようになります。
ちなみにファイルをデバイスへコピーするには ampy
を使いましょう。
import ntptime
offset = 9 * 3600
ntptime.host = 'ntp.nict.jp'
ntptime.settime(offset)
now = utime.localtime() #現在時刻の取得
print(now) #(YYYY, MM, DD, HH, MM, SS, MS)
VSCode から M5StickC にアクセスしたい
拡張機能 vscode-m5stack-mpy
をインストールしましょう。
使い方についてはこちらのページの「Quick Start」に書かれています。
ampy で M5StickC にアクセスできない
いざ ampy で M5StickC にアクセスしようとしても、次のエラーメッセージが表示されてアクセスできません。
ampy.pyboard.PyboardError: could not enter raw repl
ampy はデバイス側でREPL(対話モード)が使える状態になってないと利用できません。
そのような場合は、一旦 ターミナル(TeraTerm
(Windows)や screen
(Linux等))からデバイスにシリアル接続します。(baudrate=115200 )
接続後エンターキーを押しても無反応なはずですので、そこで何度か Ctrl
+ C
を押せば、見慣れた Python インタプリタのプロンプト(>>>
)が表示されます。
最後にその状態からターミナルをデバイスから切断すれば、ampy でアクセスできるようになります。
※ screen の場合、デタッチのみではセッションが残ってしまいますので、確実にセッションも切断しましょう(session kill PID
)
ちなみにデバイスの電源をOFF/ONしてしまうと、再度 ampy でアクセスできなくなります。
その場合はもう一度ターミナル接続してREPLが利用できる状態にする必要がありますので注意してください。
起動時にアプリを自動実行させたい
独自の main.py
を作成、デバイスへ転送し、電源を再投入するけど自動実行されない。
大抵の方はそんな状態に陥ると思います。(私もそうなりました)
ネット上の情報では対応方法を見つけることができず、大抵は「boot.py が実行された後、main.py が実行される」とだけしか書かれていません。
当方で調査、検証した結果、独自アプリを自動起動させる手段は、以下の2通りがあるようです。
##(手段1)/flash/config.json を変更する
まずは独自アプリを main.py
というファイル名で作成、デバイスにアップロード(/flash/main.py
)します。
そして /flash/config.json
を次のように変更します。
(/flash/config.json
内のキー start
の値を flow
から app
に変更するだけです)
{"server": "Flow.m5stack.com", "start": "flow", "mode": "usb", "wifi": {"ssid": "", "password": ""}}
{"server": "Flow.m5stack.com", "start": "app", "mode": "usb", "wifi": {"ssid": "", "password": ""}}
変更後に電源を再投入すれば、UIFlow のロゴマークが表示された後に main.py
が自動実行されます。
##(手段2)/flash/apps 下にアプリを配置する ※推奨
独自アプリを作成し、デバイスの /flash/apps
下にアップロードします。
※今回は /flash/apps/hoge.py
というアプリを配置しました。LCD中央に「HOGE」と表示するだけのアプリです。
※各ボタンの配置がわからない時はこちらの画像を参照してください。
一度電源をOFFし、ボタンAを押しながらパワーボタンを押して電源を投入します。
下図のように Program
が表示されます。
ボタンBを押して下図のように APP.List
を表示します。
ボタンAを押してアプリのリストを表示します。
更にボタンBを押して自動実行したいアプリ(今回は hoge
)を選択します。
アプリ選択後、ボタンAを押すと選択したアプリが実行されます。
以上の操作で、電源再投入後も選択したアプリが自動実行します。
AWS IoT Core と連携したい
無理です、諦めてください。
・・・というのは半分冗談で半分本気です。
実は執筆時点の UIFlow-v1.3.2 と M5StickC の組み合わせで SSL(TLS)接続を行うと、メモリ絡みのエラーが頻発します。(mbedtls_ssl_handshake error
や、Stack Overflow
でファームウェアがクラッシュ)
だからTSL接続が必須なIoTクラウドサービス(AWS IoT Core や Google Cloud IoT Core 等)に対しては、直接連携することが出来ません。
(将来のバージョンアップで出来るようになるかもしれませんが)
そこで直接連携出来ないのであれば、ブローカー的なものを間に挟めば良いのです。そう、MQTTブローカーです。
MQTTブローカーのオープンソース実装である「Mosquitto」をVMインスタンスやコンテナ上で稼働し、更にブリッジ機能を利用することで、AWS IoT Core に接続し、メッセージ交換ができるようになります。
環境構築
MQTTブローカーを使った AWS IoT へのブリッジ環境の構築方法は、クラメソさんのこのページが参考になると思います。
(実際の本番環境では MQTT ブローカー自身が SPOF にならないよう、K8s 上に実装する等の工夫が必要でしょう。)
更にもう一工夫
クラメソさんの記事では、bridge.conf
内のブリッジするトピックを、以下のように固定していました。
(awsiot_to_localgateway
、localgateway_to_awsiot
、both_directions
)
# Specifying which topics are bridged
topic awsiot_to_localgateway in 1
topic localgateway_to_awsiot out 1
topic both_directions both 1
ブリッジするトピックの指定ですが、実はワイルドカード(#
)を利用することでき、例えば publish されたトピック全てをブリッジする場合は次のように記述することができます。
# Specifying which topics are bridged
topic # both 1
AWS IoT の 「$aws/things/~」 へ Publish する場合
AWS IoT の場合、デバイスから Publish するトピックに $aws/things/~
を指定する場合もあります。(Shadow 等)
Mosquitto では Publish されたトピックに $
記号が含まれていると、正常に転送されない不具合(仕様?)があるようです。
そんな場合は次のように設定することで、 MQTT ブローカーから AWS IoT へ Publish するトピック全てに $aws/
を先頭に付与することが出来ます。
# Specifying which topics are bridged
topic # out 1 "" $aws/
topic # in 1
デバイスからは $aws/
を除いたトピック(things/~
)へ Publish することで、目的のトピックへ転送されます。
そもそも bridge.conf の内容が反映されない
/etc/mosquitto/conf.d
下に bridge.conf
を置いただけでは、設定が反映されない場合があるようです。(手元にある CentOS7 がそうでした)
その場合は /etc/mosquitto/mosquitto.conf
に、次の include_dir
行を追加してください。
include_dir /etc/mosquitto/conf.d
Timer で OSError 261 が発生する
マルチスレッドの実行でよく使う Timer
ですが、その際スクリプトは次のように書くと思います。
from machine import Timer
def int_handler_timer_0(t):
print('Timer0')
def int_handler_timer_1(t):
print('Timer1')
t0 = Timer(0)
t0.init(period=10000, mode=Timer.PERIODIC, callback=int_handler_timer_0)
t1 = Timer(1)
t1.init(period=5000, mode=Timer.PERIODIC, callback=int_handler_timer_1)
一見問題ないように見えるスクリプトですが、いざ実行すると OSError: 261
が発生し、Timer(1)
側がスタックしてしまいます。
検証したところ、M5StickC には Timer が2個しかなく、更にその2個は -1
、0
を指定しなければスタックしてしまうようです。
なので上記スクリプトを実行するには、次のようにそれぞれ Timer(-1)
、 Timer(0)
とする必要があります。
from machine import Timer
def int_handler_timer_0(t):
print('Timer0')
def int_handler_timer_1(t):
print('Timer1')
t0 = Timer(-1)
t0.init(period=10000, mode=Timer.PERIODIC, callback=int_handler_timer_0)
t1 = Timer(0)
t1.init(period=5000, mode=Timer.PERIODIC, callback=int_handler_timer_1)
また Timer は 2個しかありませんので、次のように 3つ目の Timer を追加すると OSError: 261
が発生し、Timer(1)
がスタックします。
from machine import Timer
def int_handler_timer_0(t):
print('Timer0')
def int_handler_timer_1(t):
print('Timer1')
def int_handler_timer_2(t):
print('Timer2')
t0 = Timer(-1)
t0.init(period=10000, mode=Timer.PERIODIC, callback=int_handler_timer_0)
t1 = Timer(0)
t1.init(period=5000, mode=Timer.PERIODIC, callback=int_handler_timer_1)
t2 = Timer(1)
t2.init(period=1000, mode=Timer.PERIODIC, callback=int_handler_timer_2)
#M5StickC向けのPythonリファレンスとかあるの?
UIFlow の Python ライブラリが GitHub リポジトリ UIFlow-Code に公開されており、そこの Wiki ページ に M5Stack(M5StickC)向けリファレンスがあります。
また、UIFlow 自体は MicroPython v1.11
をベースに開発されているとのこと(参考)なので、MicroPython v1.11 のリファレンス も役立つと思います。(ESP32 向けのリファレンスを参照してください)