Pico W PR

【Wi-Fi】RaspberryPi Pico W MicroPythonで簡易Web Serverを作る方法【チュートリアル解説】

記事内に商品プロモーションを含む場合があります

はじめに

RaspberryPi Pico W で MicroPythonを使って、簡易Webサーバーを作る方法を解説します。

ソースコードは公式サイトのチュートリアル(Getting Started) と同じですが、日本語のコメントを追加して、少し細かい内容まで解説しています。

チュートリアルのコードを見て「これどういう意味?」と思った方はぜひご覧ください。

環境

この記事で使用する環境は以下の通りです。

環境 バージョンなど 備考
開発用PCのOS Windows11 Windows10でもOKです
言語 MicroPython
開発環境 Thonny 3.3.13
ボード RaspberryPi Pico W

RaspberryPi Pico Wとの接続

配線は必要ありません。Pico W 本体のWi-Fiモジュール・LED・温度センサを使います。

使用する部品

Pico W 本体

Raspberry Pi Pico W本体です。技適対応品かどうか心配…という方は、以下のページで出品者を「共立エレショップAセレクト」(国内の有名ショップ)にして購入してください。

¥1,622 (2024/04/14 21:45時点 | Amazon調べ)
\楽天ポイント4倍セール!/
楽天市場

USBケーブル Micro-B

本体にUSBケーブルが付属していないので、別途購入が必要です。PicoW側の形状は「Micro-B」、ひと昔前のスマホと同じタイプを使います。現在主流のTypeCではないので注意が必要です。

プログラム概要

今回のプログラムの概要は以下の通りです。

  • Pico W をWi-Fiに接続する
  • ソケットを作成する
  • ブラウザからの接続待ちをする
  • Webサイトを生成してブラウザに送信する
  • (リクエストに応じて、LED操作と・Webサイトの生成をする)

実行結果

後述するプログラムを実行すると、PicoWが簡易のWebサーバーになります。

表示されるWebページでは、本体のLEDの操作や、温度センサーの温度を取得することができます。Webページなのでスマホからの操作も可能です。

実行結果のブラウザ画面
ブラウザの操作画面(PCのGoogle Chrome)

本体のLEDを点灯させた状態

利用環境によって、Pico W のIPアドレスは変わります。

IPアドレスは、プログラムの実行時の出力されるので、Thonnyなどの開発環境の出力を確認してください。

ThonnyでのIPの出力

全体コード

全体コードは以下の通りです。詳細な内容は後述する「コードのポイント」で解説します。

import network
import socket
from time import sleep
from picozero import pico_temp_sensor, pico_led
import machine

#
# Wi-Fi ルーターのSSIDとパスワードです。
# お使いの設定に書き換えてください。
#
ssid = 'NAME OF YOUR WIFI NETWORK'
password = 'YOUR SECRET PASSWORD'

#
# Wi-Fiに接続する関数です
#
def connect():
    #Connect to WLAN
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(ssid, password)
    while wlan.isconnected() == False:
        print('Waiting for connection...')
        sleep(1)
    ip = wlan.ifconfig()[0]
    print(f'Connected on {ip}')
    return ip

#
# WEBページを生成する関数です
#
def webpage(temperature, state):
    #Template HTML
    html = f"""
            <!DOCTYPE html>
            <html>
            <form action="./lighton">
            <input type="submit" value="Light on" />
            </form>
            <form action="./lightoff">
            <input type="submit" value="Light off" />
            </form>
            <p>LED is {state}</p>
            <p>Temperature is {temperature}</p>
            </body>
            </html>
            """
    return str(html)

#
# クライアント(ブラウザ)からの接続に対応する関数です
#
def serve(connection):
    #Start a web server
    state = 'OFF'
    pico_led.off()
    temperature = 0

    while True:
        client = connection.accept()[0]
        request = client.recv(1024)
        request = str(request)
        try:
            request = request.split()[ 1]
        except IndexError:
            pass

        if request == '/lighton?':
            pico_led.on()
            state = 'ON'

        elif request =='/lightoff?':
            pico_led.off()
            state = 'OFF'

        temperature = pico_temp_sensor.temp
        html = webpage(temperature, state)
        client.send(html)
        client.close()
 
#
# データをやり取りする口(ソケット)を
# 作成する関数です
#    
def open_socket(ip):
    # Open a socket
    address = (ip, 80)
    connection = socket.socket()
    connection.bind(address)
    connection.listen(1)
    return connection

#
# メインの処理部分です
#
try:
    # Wi-Fiに接続し、IPアドレスを取得します
    ip = connect()

    # IPアドレスを使って、データをやり取りするソケットを作ります
    connection = open_socket(ip)

    # ソケットを使って、クライアント(ブラウザ)からの接続を待ちます
    # (内部で無限ループ)
    serve(connection)

#
# プログラムが中断された場合は、この処理に飛び、 
# デバイスをリセットします
#
except KeyboardInterrupt:
    machine.reset()
    

Pico/Pico Wだけ(PCなし)でプログラムを実行する場合は、ファイル名を「main.py」にして、Pico/Pico W本体に保存してください。

コードのポイント

【メイン処理】Wi-Fiへの接続

メインの処理部分から、connect関数を呼び出します。

#
# メインの処理部分です
#
try:
    # Wi-Fiに接続し、IPアドレスを取得します
    ip = connect()

connect関数

端末モードで作成

connect関数ではまず、Wi-Fiを使うための「wlan」オブジェクト(変数)を作成します。引数「STA_IF」は、Pico Wが「端末」としてルーターに接続するための指定です。

他にも「network.AP_IF」が指定できますが、こちらはPico Wを「アクセスポイント」として指定するためのもので、ルーターの代わりに、Pico WにPCやスマホなどを接続させる場合に使用します。

active関数はネットワークインターフェイスを有効化するためのもので、接続や通信を行う前に「True」に指定します。

#
# Wi-Fiに接続する関数です
#
def connect():
    #Connect to WLAN
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)

Wi-Fiへの接続

wlan.connect関数を実行するとWi-Fiへの接続処理が始まります。

この関数は、接続が完了する前に次の行に進むため、isconnect関数を使って、接続が完了するまで(戻り値がFalseの間)、ループで待機するようにしています。

接続が完了すると、ifconfig関数のゼロ個目の要素にPico WのIPアドレスが返されるので、そのIPアドレスをメインの処理に返します。

wlan.active(True)
    wlan.connect(ssid, password)
    while wlan.isconnected() == False:
        print('Waiting for connection...')
        sleep(1)
    ip = wlan.ifconfig()[0]
    print(f'Connected on {ip}')
    return ip

【メイン処理】ソケットの作成

Wi-Fiへの接続処理が完了したら、open_soket関数で、ネットワークを介してクライアント(ブラウザ)とデータをやり取りするための「ソケット」を作成します。

#
# メインの処理部分です
#
try:
    # Wi-Fiに接続し、IPアドレスを取得します
    ip = connect()

    # IPアドレスを使って、データをやり取りするソケットを作ります
    connection = open_socket(ip)

open_socket関数

ソケットにはconnect関数で取得したIPアドレスと、ポート番号を指定します。ポートはWebやメールなど、用途ごとに番号が決まっており、今回はWeb用の「80」を指定します。

bind関数はIPとポートをソケットに設定、listen関数はデータのやり取りを開始するために呼び出します。

#
# データをやり取りする口(ソケット)を
# 作成する関数です
#    
def open_socket(ip):
    # Open a socket
    address = (ip, 80)
    connection = socket.socket()
    connection.bind(address)
    connection.listen(1)
    return connection

【メイン処理】ブラウザからの接続待ち

ソケットを作成してデータをやり取りする準備が完了したら、serve関数を使ってブラウザからの接続待ちと、応答処理を行います。

#
# メインの処理部分です
#
try:
    # Wi-Fiに接続し、IPアドレスを取得します
    ip = connect()

    # IPアドレスを使って、データをやり取りするソケットを作ります
    connection = open_socket(ip)

    # ソケットを使って、クライアント(ブラウザ)からの接続を待ちます
    # (内部で無限ループ)
    serve(connection)

serve関数

初期化

接続待ちを行う前に、Pico W本体のLEDと状態を「消灯」で初期化し、温度用の変数の値もゼロで初期化します。

#
# クライアント(ブラウザ)からの接続に対応する関数です
#
def serve(connection):
    #Start a web server
    state = 'OFF'
    pico_led.off()
    temperature = 0

※ 本体LEDの操作については、こちらのpico_ledについての記事もご覧ください。

接続待ち

accept関数を使ってブラウザからの接続待ちを行います。この関数は接続があるまで次の処理に進まないため、ループで待つ必要はありません。

接続があったら(処理が次の行に進んだら)、ブラウザからのデータを受信して、文字列に変換します。

    while True:
        client = connection.accept()[0]
        request = client.recv(1024)
        request = str(request)

受信データの解析(初回表示)

split関数は、文字列をスペース区切りで分割する関数です。また、関数の末尾に[ 1]がついているため、分割した1番目の文字列のみが「request」に代入されます。

        try:
            request = request.split()[ 1]
        except IndexError:
            pass

実際に、ブラウザから初めてアクセスした場合、以下の受信データが届きます。スペースで区切られて「GET」「/」の順番なので、「request」には「/」が代入されます。

b'GET␣/␣ HTTP/1.1   … ※ 以降省略

※ []の数字は0番目から始まるため、1番目は「/」になります。

LED操作とWebページの作成・送信

以降の処理では「request」の内容に応じて、LEDの操作を行います。

前述した初回表示の受信データでは「request」の内容は「/」であり、条件に合致しないので操作は行われません。

temp関数では、PicoW本体の温度センサの温度を取得できます。

取得した温度とLEDの状態は、webpage関数に渡しブラウザに表示するWebページの作成に使用します(詳細は後述)。作成したWebページ(html)はclient.send関数でブラウザに送信します。

送信後は、ブラウザとの接続を切断し、無限ループによって冒頭のaccept関数に戻ります。

        if request == '/lighton?':
            pico_led.on()
            state = 'ON'

        elif request =='/lightoff?':
            pico_led.off()
            state = 'OFF'

        temperature = pico_temp_sensor.temp
        html = webpage(temperature, state)
        client.send(html)
        client.close()

webpage関数

ブラウザに表示するWebページを生成する関数です。HTML言語でWebページを作成しており、ブラウザで見ると以下のような操作ボタン(Submit)付のページが表示されます。

{tempreture}などの{}で囲まれた部分は「プレースホルダ」とよばれ、引数で与えた温度・LEDの状態が自動的に代入されます。

実行結果のブラウザ画面
ブラウザの操作画面(Google Chrome)
#
# WEBページを生成する関数です
#
def webpage(temperature, state):
    #Template HTML
    html = f"""
            <!DOCTYPE html>
            <html>
            <form action="./lighton">
            <input type="submit" value="Light on" />
            </form>
            <form action="./lightoff">
            <input type="submit" value="Light off" />
            </form>
            <p>LED is {state}</p>
            <p>Temperature is {temperature}</p>
            </body>
            </html>
            """
    return str(html)

Webページの操作ボタンが押された時の流れ

前述の内容では、Webページの初回表示の流れを解説しましたが、ここではWebページの「LightOn」ボタンが押された時の流れを解説します。

Webページからのリクエスト送信

Webページ上のボタンは、以下のHTML対応しており、ユーザーがボタンを押すと、以下の「./lighton」の内容が「/lighton?」という文字列に自動変換されてPicoに送られます。

            <form action="./lighton">
            <input type="submit" value="Light on" />
            </form>

b'GET␣/lighton?␣ HTTP/1.1   … ※ 移行省略

Pico W での受信処理

リクエストが送信されると、Pico Wではaccept関数での接続待ちが解除され、クライアントからのデータがsplit関数で処理されます。

今回の受信データは、「GET」「/lighton?」の順になるため、requestには「/lighton?」が代入されます。

これにより、if文に合致し、led_off関数でPico W 本体のLEDが点灯、stateの状態が”ON”になったWebページが生成されて、ブラウザに送られます。

 request = request.split()

~~~ 省略 ~~~

 if request == '/lighton?':
            pico_led.on()
            state = 'ON'

        elif request =='/lightoff?':
            pico_led.off()
            state = 'OFF'

        temperature = pico_temp_sensor.temp
        html = webpage(temperature, state)
        client.send(html)
        client.close()

Light on ボタンを押した後に表示されるWebページを説明した画像
Light on ボタンを押した後に生成されるWebページ

まとめ

RaspberryPi Pico W で MicroPythonを使って、簡易Webサーバーを作る方法を解説しました。

コード・解説ともに長くなってしまいましたが、チュートリアルのコードを見て「なぜ?」と思った方の参考になればうれしいです。

お知らせ

今月号のInterfaceは『RaspberryPi 5』特集

3月25日発売のInterfaceは「RaspberryPi 5」特集!

実験などを通じた性能測定や特徴・変更点の解説。新規に追加されたI/Oボード「RP1」の特徴と謎。Pi5のはじめかたやLinuxコマンド入門などが特集されています。

CQ出版
¥1,430 (2024/03/24 17:23時点 | Amazon調べ)
\楽天ポイント4倍セール!/
楽天市場

Pico/Pico W関連のおすすめ本

RaspberryPi Pico / Pico W関連のおすすめ本を独断と偏見で3つ選んでみました。Picoやるならとりあえずこれ買っとけ的な本や、電子工作全般で使える本などを厳選しています。

見た目のいい工具をお探しの方へ

よく使うツールは『見た目』が重要。モチベもあがりますし、仲間との話のネタにもなります。以下のサイトは、デザイン性の高い工具が集めたサイトなので、興味がある方はぜひご覧ください。

質問・要望 大歓迎です

「こんな解説記事作って」「こんなことがしたいけど、〇〇で困ってる」など、コメント欄で教えてください。 質問・要望に、中の人ができる限り対応します。

使えたよ・設定できたよの一言コメントも大歓迎。気軽に足跡を残してみてください。記事を紹介したい方はブログ、SNSにバシバシ貼ってもらってOKです。

ABOUT ME
えす
現役のソフトウェアエンジニアです。 C++ C# Python を使ってます。10年ちょい設計/開発部門にいましたが、今はQAエンジニアっぽいことをしています。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

Index