Raspberry Pi Pico WH(ピンヘッダー実装済み製品)
Amazon(アマゾン)

先週、Raspberry Pi Pico WからAWS IoT CoreへMQTTSのパブリッシュがやっとできたっていう話を書きました。
よーしこれで、今週はサクッとAWS側からのトピックをサブスクライブして、LED操作テストだー!って思ってました。
実際、途中までサクッとできたのですが・・。
実はもう1つ落とし穴がありましたっていうのが、今日の話です。
Thonny経由じゃないとTLSコネクションのところで固まって、MQTT接続できない!
ということで、備忘録。
まず、現象は上記の通りですが、もう少し詳しく書くと、
ThonnyのEditor実行だとエラーなくWi-Fi接続→DNS名前解決→TLS接続→MQTTSパブリッシュ/サブスクライブ可能なのですが、
同じmain.pyで単体起動(Thonny接続せず電源だけつなぐ起動という意味)すると、
Wi-Fi接続とDNS名前解決はできるけどTLS接続ができないで固まる・・というもの。
ググってみても、ほとんどの場合「Thonnyで実行してうまくいく」ようにしか書いてなくて、
(私のリサーチ力不足もありますが、そう見えて)、
いざPico W単体起動したらうまくいかなかった、っていうときの対処がすぐに見つからず・・。
土曜日の午後まるまるつかってやっと解決できました。
Pico Wの電源はMicro USB type-B。昔の携帯電話とか、モバイルバッテリーで使ってたやつです。
マイクロBのアダプター、普段使わないので、お道具箱の奥のほうから引っ張り出してくるわけですが、
そんなこともあり、最初は「あれ、アダプター壊れてるかな」程度の感覚から始まりました。
で、さっきまでPCにつないでいたUSBケーブルで、Thonnyを接続せずに起動してみてもNG・・
ってことで、底なし沼にいることに気づきました。
この時点で、Thonny経由でプログラム実行するときと単体起動で動かすときに、挙動の差があるなんて、
ひとかけらも思わなかったので、「????」のまま、なんども起動停止、プログラム見直し・・。
単体起動だと、Wi-Fi接続とDNSの起動タイミングがずれるから、数秒間waitを入れたほうがいい、とか
TLSは時刻同期が必須だけど、単体起動の時NTPの時刻同期が遅れてTLSハンドシェイクが失敗する、とか。
なんやかんや試しながら、調べていたら
RaspberryPiフォーラムの掲示板までたどり着いて、どこの国の方か知りませんが、
全く同じ現象でハマってた人たちを見つけました。
wlan.connect() を呼ぶと接続が不安定になりやすい。ssl.SSLContext() を呼ぶとTLS初期化中にクラッシュする。wait_msg() がブロッキングしていたwait_msg() はThonnyのREPLスレッドを前提にしたブロッキング処理。めちゃくちゃ勉強になりました。
サーバーでは考えたことがないような現象が、マイコンでは割とあるあるなのかも、
という感覚を持てただけでも大きな収穫です。
time.sleep(3) などでチップ起動完了を待つようにしました。def connect_wifi():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
time.sleep(3)
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
for _ in range(60):
if wlan.isconnected():
blink(2, 0.1) # ★フェーズ1 Wi-Fi成功(起動デバッグ用)
return
time.sleep(0.5)
steady_on() # ★Wi-Fi失敗(起動デバッグ用)
while True: time.sleep(1)
チップ起動時間を稼ぐと良いらしい、というネットの情報をもとに追加しました。
RTC(内蔵時計)が未同期のままTLSを開始していた
ntptime.settime() でNTP同期を行い、正しい時刻を設定しました。
時刻同期の関数を定義し、wi-fi接続の後実行するようにしました。
def sync_time():
for _ in range(3):
try:
ntptime.settime()
blink(3, 0.1) # ★フェーズ2 NTP成功(起動デバッグ用)
return
except:
time.sleep(3)
steady_on() # ★NTP失敗(起動デバッグ用)
while True: time.sleep(1)
TLS開始直前のメモリ状態が不安定
gc.collect() を数回呼び出してから time.sleep(2) で安定化を待つようにしました。
正直、これが一番わからなかったところでした。
gcは、ガベージコレクションのこと。
メモリ領域のごみ(ガベージ)を整理するやつ。情報処理の試験でしか聞いたことがない。
お勉強はしておくものだなぁと思った次第です。
とか言いつつ、実はあんまりちゃんとわかってないです・・・笑
ネットの情報から「これを使ったらうまくいったよ」というのをもとに、おまじない的に入れてみたら
うまくいった、というレベルです・・恥ずかしながら。
def mqtt_connect():
try:
# ★TLS前準備:GC3回+ウェイト
for i in range(3):
gc.collect()
time.sleep(1)
MQTTの wait_msg() がブロッキングしていた
非ブロッキングの check_msg() に変更し、time.sleep(0.5) のループで周期的にポーリングするようにしました。
これまた難解だったのですが、
Thonny(USBシリアル)経由のときは、割り込み処理がThonnyコンソールによって調整されるので、
wait_msg()内のソケットreadが動くので、MQTTのサブスクライブができていた、ということらしい。
一方、単体起動ではThonnyコンソール上で動いていないので、割り込み調整がない。
なので、MicroPythonのイベントループがブロッキング状態で固まってしまう・・ということらしいです。
(なにせ先人たちの熱い掲示板の賜物でして、あんまり深くわかってません)
これを解消するには、「非ブロッキングポーリング化」がよいらしいので、
check_msg()をぐるぐる呼んでサブスクライブできるようにした、っていうことです。
def main():
connect_wifi()
sync_time()
client = mqtt_connect()
# 非ブロッキング受信ループ
while True:
try:
client.check_msg() # ← wait_msg() の代わり
time.sleep(0.5)
Raspberry Pi Pico WからAWS IoT CoreへMQTT Publishするまでにハマった2つの罠の話。
しばらく間が空いたら、また自分で同じ罠にハマりそうなので、備忘録として残そうかなと。
誰得なブログです。
Raspberry Pi Pico W と AWS IoT Core をつないで、センサーのデータを MQTT でパブリッシュする。
umqttのバージョン違いによるエラー地獄。
ネット上の記事を見ると、`umqtt.simple` を使うと書いてあるのですが、
GitHubから.pyをコピーして/libに置く手順になっています。
これが、libに配置しても全然うまく動かない。
TypeError: unexpected keyword argument 'ssl_params'
TypeError: unexpected keyword argument 'ca_certs'
Raspberry Pi Pico WをWi-Fiでインターネットに接続して、mipで最新のmqttをダウンロードした。
Thonnyのコマンドラインから、
import mip
mip.install("umqtt.simple")
を実行
これで `/lib/umqtt/simple.py` が配置されます。
/libがなかったら勝手に作ってくれる。
当初は `ssl_params` に PEM を渡そうとして失敗しました。
DER形式にすればすんなり動く、みたいにネット記事には書いてあったけどどうにもうまくいかず・・。
正解は DER形式に変換した証明書を Pico W に置き、SSLContext を自作して MQTTClient に渡す方法でした。
import ssl
from umqtt.simple import MQTTClient
# 証明書(DER形式)
ROOT_CA = "/certs/routeca.der"
CERT_DER = "/certs/certificate.der"
KEY_DER = "/certs/private.der"
def get_ssl_context():
"""AWS IoT 用 SSL/TLS コンテキスト作成"""
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile=ROOT_CA) # ルートCA
ctx.load_cert_chain(certfile=CERT_DER, keyfile=KEY_DER) # デバイス証明書 & 秘密鍵
return ctx
def init_mqtt_client():
ssl_ctx = get_ssl_context()
client = MQTTClient(
client_id="mypico_w",
server="xxxxxx.iot.ap-northeast-1.amazonaws.com",
port=8883,
keepalive=60,
ssl=ssl_ctx
)
client.connect()
print("MQTT Client Connected to AWS IoT Core!")
return client
↑
そもそも何でこんなことしてるかっていうと、
クルマのヘッドライト、ウィンカー、ブレーキランプにLEDを仕込んで、
スマホから点灯消灯の操作ができたらおもしろいかなぁってのを始めて、
でっかい装置でいったん完成したけど、「小型化してインターネットにつなげたいよなぁ」っていう野望がありまして。
これで基本の接続はできたので、すこしずつ「プラモのIoT」作っていきます!
今年の3月から制作を開始して、5月頃から中断している180。
シャシーのS15シルビアの流用です。
実写の180はS13ですが、このキットはコストの関係でしょうか、S15シルビアを流用しています。
サスペンションパーツは180用の新金型。
だからなのか、S15シルビアもそうだったのかわかりませんが、
軸がぶれます。
ホイールをつけたらほぼ見えないところなので、気にするほどではないですけどね。
リアサスペンション。
ロアアームもきれいに再現されています。
オイルパン、ミッションケース、ドライブシャフト、エキマニ、エキパイ、タイコ、
ぜんぶシャシーにモールドされた一体形成なので、がっつりマスキングして塗装します。
どうせ完成したら裏っ返してまで見ることはほとんどないんですけどね。
無駄に、複数のシルバーを使い分けて塗装したりなんかしています。
ちょっとした模型的演出で、タイコとか触媒部分を薄いゴールドにしてみたりして。
ブレーキディスクとキャリパーも塗装します。
今回キャリパーは黒色仕上げなので、
こんな感じで完了。
赤くしたり青くしたりするときは、さらにここからディスク側をマスクして・・っていう作業になりますが、
今回は黒なのでこれでOK。
なんとなく物足りなかったので、サスペンションスプリングをキャンディーブルーに。
黒地のショックアブソーバーにダークブルーなスプリング・・・。
全然目立ちませんでした笑
リアサスペンションユニットも塗装して
実際のクルマがこういう色っていうことではなくて、
駆動系とアームが別物ってわかるように、っていう感じで塗りました。
1つのパーツだけど、複数の部品から成り立っている感、というんですかね。
足回りのパーツがそろいました。
組み上げたらこんな感じ
マフラーはメッキパーツのままで行こうかな、と。
これが大体3月頃のお話です。9月現在、まだ完成してません・・
11月の岡崎展示会に持っていける・・かなぁ。ケッテンクラート優先かなぁ、という感じです。