Raspberry Pi Pico W で Httpサーバ(microdot)とセンサーによるHTTPリクエスト機能を同時に稼働させる

Raspberry Pi Pico W が発表されました。日本ではまだ未発売ですが、技適は取得されたようですので近いうちに国内販売がされそうです。

試しに、Webサーバ ( Microdot )とWebクライアント(urequest) を uasyncio で並列実行するコードを書きましたので、紹介します。

今回作成したコードや動作している動画は、Github で公開しています。

ytyng/rpi-pico-w-webserver-and-client: Raspberry Pi Pico W webserver and client sample code

Raspberry Pi Pico W とは

コストパフォーマンスが高いマイクロコントローラです。カテゴリとしては Arduino 等に近く、今までの Raspberry Pi のように、Linux OS を動作させるようなマシンではありません。

RP2040 というラズベリーパイ財団が開発したチップのデモボードという位置づけとなります。

実際の商品開発では、Raspberry Pi Pico で製品のR&Dを行い、実際は RP2040 を搭載した製品として生産するという流れとなると思いますが、ホビーや SOHOでは Raspberry Pi Pico をそのまま使うことも多いと思います。

実際、当社でも Raspberry Pi Pico を用いてイベント用の機材を作る場合がありますが、
RP2040 を使ったの製品を作るわけではなく、Raspberry Pi Pico をそのままケースに入れて使います。

MicroPython が動作するため、Python に慣れていれば開発は容易にできます。

商品名に W がつかない今までの機種は、ネットワーク機能はありませんでしたが、今回 Raspberry Pi Pico W となって無線チップが搭載され、コストパフォーマンスと使い勝手が最高の IoT デモボードとなりました。

↑ 左が Raspberry PI Pico, 右 が無線LAN チップが搭載された Raspberry Pi Pico W

考えられる用途

Raspberry Pi Pico W の用途で多く使われると考えられる用途は、

  • 接続させているデバイスのセンシング情報を元に、HTTP リクエストを発生させる
  • HTTP サーバを起動し、外部から HTTP リクエストを受け取って、接続されているデバイスを動作させる

この2つが主なものとなると考えられます。

今回は、この2つを Raspberry Pi Pico W の中で同時に実行する方法を書きます。

一通りのチュートリアル

Raspberry PI の公式ページが提供している PDF が充実います。

https://datasheets.raspberrypi.com/picow/connecting-to-the-internet-with-pico-w.pdf

ただ、PDF なので少し読みにくいのと、Thonny に関しては言及されていないため、Mac や Windows を普段使われている方は、この PDF だけでなく、他のサイトで紹介されているような Thonny を使ったセットアップを行うと良いでしょう。

ファームウェアの準備

https://micropython.org/download/rp2-pico-w/

上記 URL で、Raspberry Pi Pico 用の MicroPython ファームウェアの uf2 ファイルが入手できます。

最新版への直リンクはこちらです。 https://micropython.org/download/rp2-pico-w/rp2-pico-w-latest.uf2

ファームウェアのファイルをダウンロードし、 Pico へコピーしてください。

W 対応でない uf2 ファームウェアは別に存在します。そちらを使った場合、Wi-fi の機能が使えませんのでご注意ください。

コピー方法

Pico の BOOTSEL ボタンを押したまま USB で PC に接続すると、PCが Pico をストレージとして認識します。

ダウンロードした uf2 ファイルを Pico にドラッグアンドドロップでコピーすると、自動的にファームウェアがロードされ、 Pico が再起動します。

Wifi に接続する

一番最初に、Wifi に接続する必要があります。
SSID と パスワードが変数化されていれば、後は簡単なコードで接続が行えます。

接続用の関数を作っておくと便利で、他の方を見ても関数化しているようです。

StackOverflow の話題を見ると、Wifi との接続は main.py の中でやらずに boot.py の中でやったほうがいい、というコメントをいくつか見かけましたが、私は 開発のしやすさから main.py の中で行うようにしています。

Wi-fi に接続するコード

https://github.com/ytyng/rpi-pico-w-webserver-and-client/blob/main/network_utils.py

import rp2
import network
import uasyncio
import secrets


async def prepare_wifi():
"""
Prepare Wi-Fi connection.
https://datasheets.raspberrypi.com/picow/connecting-to-the-internet-with-pico-w.pdf # noqa
"""
# Set country code
rp2.country(secrets.COUNTRY)

wlan = network.WLAN(network.STA_IF)
wlan.active(True)

wlan.connect(secrets.WIFI_SSID, secrets.WIFI_PASSWORD)

for i in range(10):
status = wlan.status()
if wlan.status() < 0 or wlan.status() >= network.STAT_GOT_IP:
break
print(f'Waiting for connection... status={status}')
uasyncio.sleep(1)
else:
raise RuntimeError('Wifi connection timed out.')

# CYW43_LINK_DOWN (0)
# CYW43_LINK_JOIN (1)
# CYW43_LINK_NOIP (2)
# CYW43_LINK_UP (3)
# CYW43_LINK_FAIL (-1)
# CYW43_LINK_NONET (-2)
# CYW43_LINK_BADAUTH (-3)

wlan_status = wlan.status()

if wlan_status != network.STAT_GOT_IP:
raise RuntimeError(
'Wi-Fi connection failed. status={}'.format(wlan_status))

print('Wi-fi ready. ifconfig:', wlan.ifconfig())
return wlan

接続させているデバイスのスイッチ(センサー)情報を元に、HTTP リクエストを発生させる

Raspberry Pi Pico W Wi-Fi Doorbell tutorial (HTTP requests & IFTTT) — PiCockpit | Monitor and Control your Raspberry Pi: free for up to 5 Pis!

こちらの方が開発されているドアベルのコードが参考になります。
Youtube 動画もあってわかりやすいです。

urequests

Python でよく使う、requests ライブラリに変わり、MicroPython では似たような使い勝手の urequests ライブラリを使うことができます。

ネットワーク接続が確立されていれば、あとは urequests.get(...) 等で簡単にリクエストが発行できます。

Thonny の tools -> Manage packages からインストールできます。

HTTP サーバとして稼働させる

ソケットをそのまま使って簡易的な HTTP サーバにする

こちらの記事が参考になりました。大変わかりやすく日本語で説明されているので、Pico の初学者にもおすすめします。

Raspberry Pi Pico W で無線Lチカ

リンクされている、mimoroni 社のコードは、 Pico 用の拡張されたファームウェア

https://github.com/pimoroni/pimoroni-pico/releases

や、 Pico W 用の各種ユーティリティコードがあり、開発の参考になります。

このコードの HTTP サーバの部分は、TCP ソケットをそのまま使い、リクエスト本文の中のパス名と文字列一致して判定してい分岐を行っています。

Raspberry Pi 公式のチュートリアルPDFでもその方式で行っていました。

規模が小さいようであれば十分だと思いますが、HTTP ヘッダーを扱いたい場合や、少し規模を拡張したい場合はこの形では難しいでしょう。

Microdot を起動する

Flask や Bottle、fastApi が動けば良いのですが、現状は動作しません。
代わりに、Microdot という ウェブフレームワークがあり、使い勝手としては Flask や Bottle によく似ていて大変勝手が良いです。

こちらを使って ウェブサーバを起動してみます。

Microdot

Thonny で Tools -> Manage packages からインストールすることができます。

ネットワーク接続が確立したら、

app = Microdot()

@app.get('/')
async def _index(request):
return 'Microdot on Raspberry Pi Pico W'

app.run(port=80)

このような親しみやすいコードで HTTP サーバが起動します。

ウェブサーバとセンサーリクエストを同時に使う

Raspberry Pi Pico は、通常シングルスレッド動作です。(ちなみにCPUはデュアルコアです)

一応、 _threading という疑似スレッドができるライブラリはありますが、処理によっては本体が暴走したり固まることが多く、かなりおすすめしません。
暴走すると、最悪、何度もファームウェアのリセットをするこになり、開発体験は良くありません。

代わりに、 asyncio を使ったコルーチン処理を標準で行うことができ、こちらは安定して動作しますので、 Pico で開発する際は、基本的にメソッドはコルーチンで書くのをおすすめします。

Pico は、待機ループで sleep を使うことも多いですし、コルーチンと相性が良いと感じます。

Microdot も非同期対応の起動ができるものが既に開発されています。

Pico 上の MicroPython でのコルーチンは、通常の Python にビルトインされている asyncio を使うのではなく、
uasyncio というパッケージを使います。

Pico 用の uf2 ファームウェアに含まれていますので、別途新たなインストールは必要ありません。

例えば下記のようなコードで、asyncio が有効な処理を開始することができます。

import uasyncio

async def main():
uasyncio.create_task(any_async_method())
await other_async_method()


if __name__ == '__main__':
    uasyncio.run(main())

Pico の起動後、無線 LAN に接続した後は、スイッチ押下待機のループと、Micorodot の起動を
両方ともコルーチンで書くことで、無理なく並列動作をさせることができます。

実際に動作するコードは Github で公開しています。

rpi-pico-w-webserver-and-client/main.py at main · ytyng/rpi-pico-w-webserver-and-client

メインのコードとしてはこのようになります。

"""
Raspberry Pi Pico Web Server with Microdot and Switch Sample Code.
Pin 14 is used for switch input.
"""
import machine
import urequests
import network_utils
from microdot_asyncio import Microdot
import uasyncio


async def switch_loop():
"""
Switch listener loop
Pin 14 is used for switch input.
When press switch, send request to http web server.
"""
print('start switch_loop')
switch_pin = machine.Pin(14, machine.Pin.IN, machine.Pin.PULL_DOWN)

while True:
current_state = switch_pin.value()
if current_state:
# Change the URL to your own server, IFTTT, Slack, etc.
response = urequests.get('https://example.com/')
print(response.content)
response.close()
await uasyncio.sleep(2)
else:
await uasyncio.sleep(0.1)

async def run_web_server():
"""
Start microdot web server
https://microdot.readthedocs.io/en/latest/index.html
"""
app = Microdot()
led_pin = machine.Pin('LED', machine.Pin.OUT)

@app.get('/')
async def _index(request):
return 'Microdot on Raspberry Pi Pico W'

@app.get('/led/<status>')
async def _led(request, status):
"""
/led/on : LED ON
/led/off : LED OFF
"""
if status == 'on':
led_pin.on()
return 'LED turned on'
elif status == 'off':
led_pin.off()
return 'LED turned off'
return 'Invalid status.'

print('microdot run')
app.run(port=80)


async def main():
wlan = await network_utils.prepare_wifi()
print('LED ON: http://{}/led/on'.format(wlan.ifconfig()[0]))

uasyncio.create_task(switch_loop())
await run_web_server()


if __name__ == '__main__':
uasyncio.run(main())
Current rating: 5

コメント

コメントを投稿
コメントするには TORICO-ID にログインしてください。
ログイン コメント利用規約