ガイド · 読了まで21分

Raspberry Pi Pico WのWi-Fi入門:MicroPythonでWebサーバーを作る

Raspberry Pi Pico WのWi-Fiガイドです。正しいUF2、network.WLANとステータスコード、LEDを操作するWebサーバー、センサーのライブ表示ページ、urequestsでのAPI呼び出し、NTPでの時刻合わせを解説します。

Raspberry Pi Pico WでWi-Fiを使うのは、スマホから操作できる小さなデバイスを作るいちばん安くて簡単な方法の1つです。Pico Wは、おなじみのRaspberry Pi Picoに2.4 GHzのWi-FiとBluetoothを追加したバージョンで、MicroPythonの数行のコードで家庭のネットワークにつながり、自分のWebページを配信し、インターネットから天気や時刻を取得できます。このガイドでは、Pico WをゼロからWi-Fiに接続し、スマホからLEDを点灯できるWebサーバーを作り、さらにセンサーデータをリアルタイムに表示するページを作ります。

最後まで読むと、正しいUF2ファイルの選び方、network.WLANでの接続とステータスコードの読み方、urequestsを使ったAPIからのデータ取得、NTPでの時刻合わせ、そして同じことをArduino IDEで行う方法がわかります。Picoが初めてなら、先にRaspberry Pi Picoとは?を読んでおくと理解しやすく、基本的な命令はMicroPythonの基本のガイドで説明しています。

Raspberry Pi Pico WH:Wi-Fi付きでピンヘッダーがはんだ付け済みのPico W
Raspberry Pi Pico WHです。金属シールドの下にInfineon CYW43439無線チップがあり、USBポートと反対側の端にPCBアンテナがあります。名前の最後の「H」は、ピンヘッダーがはんだ付けされた状態で届くことを意味します。

Raspberry Pi Pico Wとは?Picoとの違いは?

Pico Wは、Picoと同じRP2040プロセッサ、同じサイズ、同じ40ピンの配置を使っています。違いはボードに追加されたInfineon CYW43439無線チップで、2.4 GHzの802.11n Wi-FiとBluetooth 5.2を提供します。プロセッサはこのチップとボード内部のSPI接続で通信しますが、使う側が扱うのはMicroPythonのnetworkモジュールだけです。Raspberry Piはその後、RP2350プロセッサを搭載したPico 2 Wを発売しました。無線チップは同じなので、この記事のコードはすべてPico 2 Wでも動作します。

項目Pico WPico 2 WESP32(WROOM-32)
プロセッサRP2040、デュアルコアCortex-M0+、133 MHzRP2350、デュアルコアCortex-M33(またはRISC-V)、150 MHzXtensa LX6、デュアルコア、240 MHz
RAM264 KB520 KB520 KB
フラッシュメモリ2 MB4 MB通常は4 MB
Wi-Fi2.4 GHz、802.11n(CYW43439)2.4 GHz、802.11n(CYW43439)2.4 GHz、802.11 b/g/n(チップに内蔵)
Bluetooth5.2(ClassicとBLE)5.2(ClassicとBLE)4.2(ClassicとBLE)
アナログ入力3チャンネル、12ビット3チャンネル、12ビット18チャンネル、12ビット
MicroPythonのファイルRPI_PICO_W(.uf2)RPI_PICO2_W(.uf2)ESP32_GENERIC(.bin)

スペック上はESP32のほうが高速で、Arduinoの世界でも作例が豊富です。一方、Pico Wの強みは、公式で成熟したMicroPythonのサポート、整理されたドキュメント、そして「起動時にこのレベルでなければならない」といった落とし穴のないピンです。Pythonで学ぶのが好きならPico Wは最適な選択で、Arduinoのコードと既製のライブラリを重視するならESP32のほうが使いやすいかもしれません。2つで迷っている場合は、ArduinoとESP32の違いは?の比較が参考になります。

Pico Wの無線チップとピンの違い

Pico Wの外に出ているピンはPicoとまったく同じです。GP0〜GP22とGP26〜GP28を自由に使え、アナログ入力も同じくGP26、GP27、GP28です。変わるのは、ボード内部にとどまる4本のピンです。Picoでは内蔵LED、電源モード、USB検出を担当していたこれらのピンが、Pico Wでは無線チップとの通信用に予約されていて、LED自体も無線チップに直接つながっています。ボードの完全なピンマップはピン配置ツールで確認できます。

内部ピンPicoでの役割Pico Wでの役割
GP23電源モード(SMPS)無線チップの有効化(WL_ON)
GP24USB電圧の検出(VBUS)無線のデータと割り込みの信号線(WL_D)
GP25内蔵LED無線チップのチップセレクト(WL_CS)
GP29VSYS電圧の測定(ADC3)無線のクロック信号線(WL_CLK)とVSYSの測定
内蔵LEDGP25:Pin(25)無線チップのWL_GPIO0:Pin("LED")
Pico Wの内蔵LEDをPin(25)で点灯しようとしないでください。このピンは、今は無線チップのチップセレクト線になっています。正しい書き方はPin("LED", Pin.OUT)で、同じ1行がPico、Pico W、Pico 2 Wのどれでも動作します。また、ボードの端にあるアンテナ部分の下やすぐ近くに、金属、電池、配線の束を置かないでください。アンテナがふさがれると、信号がはっきりと弱くなります。

ステップ1:Pico W用の正しいUF2ファイルを書き込む

いちばん多い間違いは、無印のPico用のMicroPythonファイルをPico Wに書き込んでしまうことです。ボードは動作し、Thonnyのシェルにプロンプトも表示されますが、import networkと入力した途端に「ImportError: no module named 'network'」(networkモジュールがない)というエラーが出ます。無印のPico用のビルドには無線ドライバが入っていないからです。ボードごとに専用のUF2ファイルがあります。

ボードMicroPythonのUF2ファイルBOOTSELモードで表示されるドライブ
Raspberry Pi PicoRPI_PICO-….uf2RPI-RP2
Raspberry Pi Pico W / WHRPI_PICO_W-….uf2RPI-RP2
Raspberry Pi Pico 2RPI_PICO2-….uf2RP2350
Raspberry Pi Pico 2 WRPI_PICO2_W-….uf2RP2350
  1. micropython.orgのダウンロードページでRPI_PICO_Wのボード(Pico 2 WならRPI_PICO2_W)を探し、「preview」と書かれていない最新の安定版の.uf2ファイルをダウンロードします。
  2. Pico WのBOOTSELボタンを押したまま、データ通信用のUSBケーブルをパソコンに挿し、そのあとボタンを離します。
  3. パソコンにRPI-RP2というドライブが表示されます。ダウンロードしたファイルをそこへドラッグすると、コピーが終わった時点でドライブは自動的に消え、ボードがMicroPythonで再起動します。
  4. Thonnyを開き、右下のインタプリタメニューからMicroPython (Raspberry Pi Pico)を選びます。Thonnyの「MicroPythonをインストールまたは更新」ウィンドウでPico W / Pico WHのバリアントを選べば、同じインストールをワンクリックで行うこともできます。
  5. シェルにimport networkと入力します。エラーが出なければ、無線に対応した正しいファームウェアがインストールされています。
どのボードとバージョンを使っているかわからない場合は、シェルにimport os; print(os.uname())と入力します。machineの欄に「Raspberry Pi Pico W with RP2040」と表示されていれば正解で、releaseの欄にはMicroPythonのバージョンが表示されます。ファームウェアを更新するには、同じUF2の手順を繰り返すだけです。Picoに保存したファイルは、通常そのまま残ります。

ステップ2:network.WLANでWi-Fiに接続する

Pico Wには2つの無線インターフェースがあります。WLAN.IF_STA(ステーション)モードではスマホのように家のルーターに接続し、WLAN.IF_AP(アクセスポイント)モードでは自分のWi-Fiネットワークを作ります。まずはステーションモードから始めましょう。下のコードをThonnyの新しいファイルに貼り付け、ネットワーク名とパスワードを入力して、wifi.pyという名前でPicoに保存します。以降の例はすべてimport wifiでこのファイルを使うので、パスワードは1か所にまとめておけます。MicroPython 1.24より古いバージョンを使っている場合は、これらの定数をnetwork.STA_IFとnetwork.AP_IFと書いてください。古い名前は新しいバージョンでも動作します。

Python
1# wifi.py: Pico W'ye bu adla kaydet / save it to the Pico W with this name
2import network
3import time
4
5SSID = "AgAdi"            # Wi-Fi adın / your Wi-Fi name
6PASSWORD = "Sifre123"     # Wi-Fi şifren / your Wi-Fi password
7HOSTNAME = "picow"        # http://picow.local adresi için / for http://picow.local
8
9STATUS = {
10    0: "Boşta / idle",
11    1: "Ağa katılıyor / joining",
12    2: "IP bekleniyor / waiting for IP",
13    3: "Bağlandı / connected",
14    -1: "Bağlantı başarısız / connection failed",
15    -2: "Ağ bulunamadı / network not found",
16    -3: "Şifre hatalı / wrong password",
17}
18
19
20def connect(timeout=20):
21    network.country("TR")             # ülke kodu / country code
22    network.hostname(HOSTNAME)        # ağdaki adı / name on the network
23    wlan = network.WLAN(network.WLAN.IF_STA)
24    wlan.active(True)
25    wlan.config(pm=network.WLAN.PM_NONE)   # güç tasarrufunu kapat / disable power saving
26    if not wlan.isconnected():
27        wlan.connect(SSID, PASSWORD)
28        for _ in range(timeout):
29            code = wlan.status()
30            print(code, STATUS.get(code, "?"))
31            if code < 0 or code == 3:
32                break
33            time.sleep(1)
34    if wlan.status() != 3:
35        raise RuntimeError("Wi-Fi: " + STATUS.get(wlan.status(), "?"))
36    print("IP:", wlan.ifconfig()[0])
37    return wlan
38
39
40if __name__ == "__main__":
41    connect()

重要な行を見ていきましょう。network.country("TR")は、どの国のチャンネル規則に従うかを無線チップに伝えます。TRは自分の国の2文字の国コードに置き換えてください(日本ならJP、ほかにGBやUSなど)。省略すると、一部のルーターが使う12チャンネルと13チャンネルのネットワークが見つけにくくなることがあります(日本のルーターは12〜13チャンネルを自動で選ぶこともあります。古いファームウェアでは同じ設定をrp2.country()で行います)。なお、日本でも2.4 GHz帯の規則はほぼ同じですが、技適マークのない無線機器の使用は電波法で制限されています。wlan.config(pm=network.WLAN.PM_NONE)は省電力機能をオフにします(古い例では同じ設定をpm=0xa11140と書いています)。Pico Wは初期設定で省エネ動作をするため、Webサーバーの応答が遅れたり、数時間後に接続が切れたりすることがあります。ループは1秒ごとにステータスコードを表示し、3になるか負のエラーコードが返ると止まります。こうすればボードがいつまでも待ち続けることはなく、何が問題だったかも画面で確認できます。

wlan.status()のステータスコードの意味は?

接続トラブルの多くは、wlan.status()が返す数値を見れば数秒で解決します。MicroPythonはnetworkモジュールに、これらの数値に対応する定数も用意しています。両方を下の表にまとめました。

コード定数意味対処法
0STAT_IDLE未接続で、何も開始していないwlan.active(True)とwlan.connect()を呼び出したか確認する
1STAT_CONNECTINGネットワークに参加中数秒待つ
2(定数なし)参加済みで、ルーターからIPアドレスが割り当てられるのを待っている時間がかかる場合は、ルーターのDHCP設定を確認する
3STAT_GOT_IPIPアドレスを取得して接続済み準備完了
-1STAT_CONNECT_FAIL接続に失敗電波の状態と電源を確認して再試行する
-2STAT_NO_AP_FOUNDネットワークが見つからないネットワーク名、2.4 GHz帯、距離を確認する
-3STAT_WRONG_PASSWORD認証に失敗大文字と小文字に注意してパスワードを入力し直す

コード-2が出る場合、Pico Wからネットワークが見えていません。周囲のネットワークを一覧表示すると、原因がすぐにわかります。自分のネットワークが一覧にない場合は、5 GHzで電波を出しているか、信号が弱すぎるかのどちらかです。RSSIが-70 dBmより良ければ(たとえば-55 dBm)余裕があり、-80 dBm前後になると接続が切れ始めます。

Python
1import network
2
3wlan = network.WLAN(network.WLAN.IF_STA)
4wlan.active(True)
5for ssid, bssid, channel, rssi, security, hidden in wlan.scan():
6    print(ssid.decode(), "| kanal / channel:", channel, "| RSSI:", rssi, "dBm")
Pico Wが接続できるのは2.4 GHzのネットワークだけです。ルーターが2つの帯域を同じ名前で出していて接続できない場合は、スマホのテザリングを2.4 GHzに設定して試してください。そちらで動けば、問題はコードではなくルーターの設定にあります。学校や職場の、ユーザー名を求められるネットワーク(WPA2-Enterprise)は、Pico Wでは使えません。さらに詳しいヒントとして、ESP32がWi-FiにつながらないときのチェックリストがPico Wにもそのまま当てはまります。

ステップ3:MicroPythonで作るシンプルなWebサーバー(LEDのオン/オフページ)

次は、Pico Wをスマホから開ける小さなWebサーバーにしてみましょう。追加のライブラリは不要で、MicroPythonのsocketモジュールだけで十分です。下のコードをmain.pyとして保存して実行します。シェルに表示されたアドレス(たとえばhttp://192.168.1.42)を、同じWi-Fiネットワークにつないだスマホのブラウザに入力すると、ページのONボタンとOFFボタンでボード上のLEDを切り替えられます。

MicroPython
1import socket
2from machine import Pin
3import wifi
4
5wlan = wifi.connect()
6led = Pin("LED", Pin.OUT)              # Pico W dahili LED / Pico W onboard LED
7led_on = False
8
9
10def page(on):
11    state = "AÇIK / ON" if on else "KAPALI / OFF"
12    return """<!DOCTYPE html>
13<html><head><meta charset="utf-8">
14<meta name="viewport" content="width=device-width, initial-scale=1">
15<title>Pico W LED</title></head>
16<body style="font-family:sans-serif;text-align:center">
17<h1>Pico W LED</h1>
18<p>Durum / State: <b>""" + state + """</b></p>
19<p><a href="/on"><button style="font-size:24px">AÇ / ON</button></a>
20<a href="/off"><button style="font-size:24px">KAPAT / OFF</button></a></p>
21</body></html>"""
22
23
24addr = socket.getaddrinfo("0.0.0.0", 80)[0][-1]
25server = socket.socket()
26server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)   # EADDRINUSE hatasını önler / prevents EADDRINUSE
27server.bind(addr)
28server.listen(2)
29print("Tarayıcıda aç / open in browser: http://" + wlan.ifconfig()[0])
30
31while True:
32    client, remote = server.accept()           # bir tarayıcı bekle / wait for a browser
33    try:
34        request = client.recv(1024)            # b"GET /on HTTP/1.1 ..."
35        parts = request.split(b" ")
36        path = parts[1] if len(parts) > 1 else b"/"
37        if path == b"/on":
38            led.on()
39            led_on = True
40        elif path == b"/off":
41            led.off()
42            led_on = False
43        if path in (b"/", b"/on", b"/off"):
44            client.sendall("HTTP/1.0 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n")
45            client.sendall(page(led_on))
46        else:
47            client.sendall("HTTP/1.0 404 Not Found\r\n\r\n")   # ör. /favicon.ico / e.g. /favicon.ico
48    except OSError as e:
49        print("Bağlantı hatası / connection error:", e)
50    finally:
51        client.close()

仕組みを見てみましょう。bindとlistenでPico Wはポート80(HTTP)で待ち受けを始め、accept()はブラウザが接続してくるまで待ちます。ブラウザから届くリクエストの1行目は「GET /on HTTP/1.1」のような形で、コードはこの行をスペースで区切って要求されたパス(/on)を取り出し、それに応じてLEDを設定します。続いてHTTPヘッダー(「HTTP/1.0 200 OK」とコンテンツタイプ)、空行、最後にHTMLページを送信し、接続を閉じます。meta viewportの行はスマホでページが正しく拡大縮小されるようにし、charset=utf-8は日本語などの特殊な文字を正しく表示させます。

パス動作
/ページとLEDの現在の状態を表示する
/onLEDを点灯して、ページをもう一度送信する
/offLEDを消灯して、ページをもう一度送信する
/favicon.icoなど404を返す。ブラウザはタブのアイコン用に、このパスを自動的に要求する
Thonnyでコードを停止して再び実行すると、「OSError: [Errno 98] EADDRINUSE」(アドレス使用中)というエラーが出ることがあります。前回の実行で残ったソケットが、まだポート80をつかんでいるためです。コード内のSO_REUSEADDRの行でたいていは防げますが、それでも出る場合は、シェルでCtrl+Dを押してソフトリセットするか、ボードのUSBケーブルを抜いて挿し直してください。

LEDの代わりにリレーや外付けLEDをつなぐ

内蔵LEDで動作したら、同じページで実際の機器を操作するのに変更するのは1行だけです。Pin("LED", Pin.OUT)を、たとえばPin(16, Pin.OUT)に置き換えます。外付けLEDに合う抵抗値は「LED用抵抗」ツールで計算できます。3.3 Vのピンなら220〜330 Ωで十分です。リレーモジュールを使う場合は、5 VのVBUSピンから電源を取り、IN端子をGPIOにつなぎます。多くのリレーモジュールはLOW信号でオンになります。その場合はled.on()とled.off()の行を入れ替えてください。リレーの配線の詳細はリレーモジュールとは?を参照してください。

部品部品のピンPico Wのピン(物理ピン番号)
外付けLEDアノード(+)、220 Ωの抵抗を通して接続GP14(19)
外付けLEDカソード(−)GND(18)
DHT22VCC / DATA / GND3V3(OUT)(36) / GP15(20) / GND(38)
リレーモジュールVCCVBUS、USBからの5 V(40)
リレーモジュールINGP16(21)
リレーモジュールGNDGND(23)
商用電源につながったランプをリレーで切り替える場合は、必ず電源を切った状態で配線し、むき出しの導体を残さず、回路を閉じたケースに収めてください。商用電源の電圧は命にかかわります。自信がなければ、まず12 VのLEDテープで試しましょう。5 Vのリレーモジュールの中には、3.3 Vの信号では確実に切り替わらないものがあります。リレーがカチッと鳴らない場合は、トランジスタを追加するか、3.3 V対応のモジュールを選んでください。

ステップ4:センサーデータをWebページにリアルタイム表示する

LEDのページは、ボタンを押すたびにページ全体を読み込み直していました。測定値を表示するページでは、再読み込みせずに値が更新されるほうがずっと快適です。そこで2つのパスを使います。/はページそのものを、/dataは測定値だけをJSON形式で送ります。ページ内の小さなJavaScriptが2秒ごとに/dataを読み取り、数値を書き換えます。この例では、追加の部品が不要な内蔵温度センサーと、Wi-Fiの信号強度(RSSI)を表示します。また、MicroPythonのasyncioモジュールでサーバーを作るので、Pico Wはリクエストを待っている間も測定を続けられます。

MicroPython
1import asyncio
2import json
3import time
4from machine import ADC, Pin
5import wifi
6
7wlan = wifi.connect()
8sensor = ADC(4)                        # dahili sıcaklık sensörü / internal temperature sensor
9led = Pin("LED", Pin.OUT)
10start = time.time()
11readings = {"temp": 0, "rssi": 0, "uptime": 0}
12
13HTML = """<!DOCTYPE html>
14<html><head><meta charset="utf-8">
15<meta name="viewport" content="width=device-width, initial-scale=1">
16<title>Pico W</title></head>
17<body style="font-family:sans-serif;text-align:center">
18<h1>Pico W</h1>
19<p>Sıcaklık / Temperature: <b id="t">-</b> °C</p>
20<p>Wi-Fi sinyali / signal: <b id="r">-</b> dBm</p>
21<p>Çalışma süresi / uptime: <b id="u">-</b> s</p>
22<script>
23async function update() {
24  try {
25    const d = await (await fetch("/data")).json();
26    document.getElementById("t").textContent = d.temp;
27    document.getElementById("r").textContent = d.rssi;
28    document.getElementById("u").textContent = d.uptime;
29  } catch (e) {}
30}
31update();
32setInterval(update, 2000);
33</script>
34</body></html>"""
35
36
37def read_temp():
38    volts = sensor.read_u16() * 3.3 / 65535
39    return round(27 - (volts - 0.706) / 0.001721, 1)
40
41
42async def measure():
43    while True:
44        readings["temp"] = read_temp()
45        readings["rssi"] = wlan.status("rssi")      # yalnız STA modunda / STA mode only
46        readings["uptime"] = time.time() - start
47        led.toggle()                                # kalp atışı / heartbeat
48        await asyncio.sleep(2)
49
50
51async def handle(reader, writer):
52    try:
53        line = await reader.readline()              # b"GET /data HTTP/1.1"
54        while True:                                 # başlıkları atla / skip the headers
55            header = await reader.readline()
56            if header in (b"\r\n", b""):
57                break
58        parts = line.split(b" ")
59        path = parts[1] if len(parts) > 1 else b"/"
60        if path == b"/":
61            writer.write(b"HTTP/1.0 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n")
62            writer.write(HTML.encode())
63        elif path == b"/data":
64            writer.write(b"HTTP/1.0 200 OK\r\nContent-Type: application/json\r\n\r\n")
65            writer.write(json.dumps(readings).encode())
66        else:
67            writer.write(b"HTTP/1.0 404 Not Found\r\n\r\n")
68        await writer.drain()
69    except OSError as e:
70        print("Bağlantı hatası / connection error:", e)
71    finally:
72        writer.close()
73        await writer.wait_closed()
74
75
76async def main():
77    await asyncio.start_server(handle, "0.0.0.0", 80)
78    print("Tarayıcıda aç / open in browser: http://" + wlan.ifconfig()[0])
79    await measure()
80
81
82asyncio.run(main())

asyncio.start_server()は、ブラウザから新しい接続が来るたびにhandle関数を別のタスクとして実行し、measure()タスクはバックグラウンドで2秒ごとに測定して内蔵LEDを反転させます。LEDの点滅で、ボードがフリーズしていないこともわかります。注意:asyncioで書くときは、送信する前に.encode()で文字列をバイト列に変換してください。そうしないと、日本語などの特殊な文字を含むページでエラーになります。内蔵温度センサーはチップ自体の温度を測るので、値にはノイズがあり、室温とは数度ずれます。コード中の式27 − (V − 0.706) / 0.001721は、RP2040のデータシートに由来します。正確な室温を測るには、外付けのセンサーを使ってください。

DHT22(AM2302)温湿度センサーモジュール
DHT22モジュールのピンはVCC、DATA、GNDの3本です。ピンの並びはメーカーによって異なることがあるので、基板上の表記を確認してください。モジュールはPico Wの3.3 Vで直接動作します。

本格的に温度と湿度を測るなら、DHT22をつなぎます(DATAピンをGP15へ)。DHTセンサーのドライバはPico Wのファームウェアに組み込まれているので、追加で書き込むものはありません。センサーのコードのmeasure()関数で、readings["temp"] = read_temp()の行を下のread_sensor()の呼び出しに置き換え、HTMLに湿度用の行を1つ追加します。DHT22は2秒より短い間隔で読み取らないようにしますが、これはちょうどループの間隔と同じです。センサーについて詳しくはDHT11温湿度センサーを参照してください。

MicroPython
1import dht
2from machine import Pin
3
4dht_sensor = dht.DHT22(Pin(15))        # DATA ucu GP15'te / DATA pin on GP15
5
6
7def read_sensor():
8    try:
9        dht_sensor.measure()            # ölçümü başlat / trigger a reading
10        readings["temp"] = dht_sensor.temperature()   # °C
11        readings["hum"] = dht_sensor.humidity()       # % bağıl nem / % relative humidity
12    except OSError as e:
13        print("DHT22 okunamadı / read failed:", e)

ステップ5:urequestsでAPIを呼び出す(インターネットからデータを取得)

Pico Wはサーバーだけでなく、クライアントにもなれます。オンラインのサービスにリクエストを送り、その応答を読み取れるのです。Pico Wのファームウェアにはrequestsモジュールが組み込まれていて、古い例で見かけるurequestsという名前も同じモジュールを指すので、どちらの書き方でも動作します。下の例では、APIキーが不要な無料の天気サービスOpen-Meteoから、イスタンブールの現在の気温、湿度、風速を取得します。自分の都市に合わせてLATとLONを変えるだけで使えます。たとえば東京なら35.68と139.69、ロンドンなら51.51と-0.13です。

Python
1import requests
2import wifi
3
4wifi.connect()
5
6# İstanbul; kendi konumunu yaz / Istanbul; put your own location
7LAT, LON = 41.01, 28.98
8URL = ("https://api.open-meteo.com/v1/forecast?latitude={}&longitude={}"
9       "&current=temperature_2m,relative_humidity_2m,wind_speed_10m").format(LAT, LON)
10
11response = requests.get(URL)
12try:
13    if response.status_code == 200:
14        current = response.json()["current"]
15        print("Sıcaklık / temperature:", current["temperature_2m"], "°C")
16        print("Nem / humidity:", current["relative_humidity_2m"], "%")
17        print("Rüzgâr / wind:", current["wind_speed_10m"], "km/h")
18    else:
19        print("HTTP hatası / HTTP error:", response.status_code)
20finally:
21    response.close()                   # soketi ve belleği serbest bırak / free the socket and memory

このコードには注意点が3つあります。1つ目は、応答を受け取るたびにresponse.close()を呼ぶことです。閉じていない応答はソケットとメモリを消費し、数回リクエストすると「OSError: [Errno 12] ENOMEM」(メモリ不足)が発生します。2つ目は、response.json()が応答全体をメモリに読み込むことです。Pico WのRAMは全部で264 KBしかないので、大きなJSONを返すAPIでは、必要な項目だけを要求するURL(ここでのcurrentパラメーターのような)を使ってください。3つ目は、HTTPS接続では暗号化のために追加のメモリが必要になることです。ループの中で定期的にリクエストを送る場合は、その合間にgc.collect()を入れると、メモリの断片化を減らせます。同じ方法で、Telegramにメッセージを送ったり、スプレッドシートにデータを書き込んだり、為替レートを取得したりできます。Telegramボットの設定はESP32のTelegramボットと同じです。

ステップ6:NTPでインターネットから正確な時刻を取得する

Pico Wの内部にはリアルタイムクロック(RTC)がありますが、バックアップ用の電池はありません。そのため、ボードの電源を入れるたびに時計は2021年1月1日に戻ります。Wi-Fiにつながっていれば、インターネットのNTPサーバーから正しい時刻を取得するのは簡単です。組み込みのntptimeモジュールのsettime()関数が、内部時計をUTCに合わせます。コードは一定の時差を足して現地時刻を求めます。例ではトルコ用に3時間(10,800秒)を使っているので、UTC_OFFSETを自分のタイムゾーンに合わせてください。たとえば日本(UTC+9、夏時間なし)なら9 * 3600、冬のロンドンなら0、ニューヨークなら-5 * 3600です。

Python
1import time
2import ntptime
3import wifi
4
5wifi.connect()
6ntptime.host = "pool.ntp.org"          # varsayılan sunucu / default server
7UTC_OFFSET = 3 * 3600                  # Türkiye UTC+3; kendi saat dilimini yaz / set your own time zone
8
9for attempt in range(5):
10    try:
11        ntptime.settime()              # dahili saati UTC'ye ayarlar / sets the internal clock to UTC
12        print("Saat ayarlandı / time set")
13        break
14    except OSError as e:
15        print("NTP hatası / NTP error:", e)
16        time.sleep(2)
17
18DAYS = ("Pzt/Mon", "Sal/Tue", "Çar/Wed", "Per/Thu", "Cum/Fri", "Cmt/Sat", "Paz/Sun")
19
20while True:
21    y, mo, d, h, mi, s, wd, yd = time.localtime(time.time() + UTC_OFFSET)
22    print("%02d.%02d.%d %s %02d:%02d:%02d" % (d, mo, y, DAYS[wd], h, mi, s))
23    time.sleep(1)

ntptimeは初期設定でpool.ntp.orgサーバーを使い、応答を1秒待ちます。応答がなければ「OSError: [Errno 110] ETIMEDOUT」(タイムアウト)が発生するので、コードは5回試します。内部時計は時間とともに数秒ずれることがあるため、長時間動かすプロジェクトでは1日に1回settime()を呼び直してください。夏時間のある国では時差が年に2回変わるので、コードで日付をチェックして対応する必要があります(日本には夏時間はありません)。時刻をディスプレイに表示したい場合は、OLEDディスプレイやI2C LCDの記事にあるMicroPythonの例と、このコードを組み合わせてください。ESP32版はESP32のNTPにあります。

自分のネットワークを作る(APモード)とpicow.localのアドレス

科学コンテストの展示ブースや庭など、ルーターがない場所では、Pico Wが自分でWi-Fiネットワークを発信できます。アクセスポイントモードでは、スマホを直接「PicoW-BlueGrays」ネットワークにつなぎ、http://192.168.4.1でページを開きます。Webサーバーのコードはそのまま動作します。wlan = wifi.connect()の代わりに下の行を書き、wlan = apを追加するだけです。パスワードは8文字以上にする必要があります。センサーの例にあるRSSIの行はステーションモードでしか動作しないので、APモードでは削除してください。このモードのPico Wはインターネットにアクセスできないので、NTPとAPIの例は動作しません。

Python
1import network
2
3ap = network.WLAN(network.WLAN.IF_AP)
4ap.config(ssid="PicoW-BlueGrays", password="bluegrays123")   # şifre en az 8 karakter / at least 8 characters
5ap.active(True)
6while not ap.active():
7    pass
8print("Ağ hazır / network ready:", ap.ifconfig()[0])          # genellikle 192.168.4.1 / usually 192.168.4.1

毎回シェルでIPアドレスを確認するのが面倒なら、代わりに名前を使えます。最近のMicroPythonはPico WでmDNSに対応していて、wifi.pyのnetwork.hostname("picow")の行のおかげで、ボードにはhttp://picow.localでもアクセスできます。このアドレスはWindows、macOS、Linux、iPhoneでは通常使えますが、一部のAndroidスマホでは.localのアドレスを開けないので、その場合はIPアドレスを使ってください。IPアドレスが変わらないようにしたいなら、ルーターの設定でPico WのMACアドレスに固定IPを割り当てる(DHCPの予約)のがいちばん確実です。

Pico 2 Wについての注意:何が変わる?

Pico 2 Wは、Pico Wと同じサイズとピン配置で、同じCYW43439無線チップを使っていますが、プロセッサはより高速なRP2350です。ソフトウェア面での違いはファームウェアだけで、micropython.orgからRPI_PICO2_Wのファイルをダウンロードします。BOOTSELを押しながら接続すると、ドライブはRP2350という名前で表示されます。この記事のMicroPythonのコードは、すべてそのまま動作します。RAMが520 KBあるので、HTTPSのリクエスト、大きなJSONの応答、長いHTMLページも、Pico 2 Wならずっと余裕をもって収まります。Arduino IDEでは、ボードとしてRaspberry Pi Pico 2Wを選びます。1つ注意点があります。RP2350の初期のバージョンには、内部プルダウン抵抗に関する既知のチップのエラッタ(E9)があります。ボタンはGNDに配線してPULL_UPを使うのが、いちばんトラブルの少ない方法です。2つのコアやPIOなどの高度な機能については、PicoのPIOとデュアルコアを参照してください。

別の方法:Arduino IDEでPico Wをプログラムする(arduino-pico)

C++とArduinoのライブラリのほうがなじみがあるなら、Earle Philhower氏のarduino-picoコアを使って、Arduino IDEからPico Wをプログラムできます。ESP32用と互換性のあるWiFiライブラリとWebServerライブラリが付属しているので、ESP32向けに書かれたWebサーバーのコードが、少しの変更でPico Wでも動作します。

  1. Arduino IDEで「ファイル」>「基本設定」を開き、「追加のボードマネージャのURL」欄にhttps://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.jsonを追加します。
  2. ボードマネージャでpicoを検索し、Earle F. Philhower氏のRaspberry Pi Pico/RP2040/RP2350パッケージをインストールします。
  3. 「ツール」>「ボード」からRaspberry Pi Pico Wを選びます(Pico 2 WならRaspberry Pi Pico 2W)。
  4. 最初の書き込みでは、BOOTSELを押したままボードを接続します。Arduino IDEはボードをUF2ドライブとして認識します。2回目以降は、「ツール」>「ポート」に表示されるシリアルポートを選ぶだけです。
  5. 書き込みが終わったら、シリアルモニタを115200ボーで開き、IPアドレスを確認します。
C++ (Arduino IDE)
1#include <WiFi.h>
2#include <WebServer.h>
3#include <LEAmDNS.h>
4
5const char* ssid = "AgAdi";          // Wi-Fi adın / your Wi-Fi name
6const char* password = "Sifre123";   // Wi-Fi şifren / your Wi-Fi password
7
8WebServer server(80);
9bool ledOn = false;
10
11void sendPage() {
12  String html = "<!DOCTYPE html><html><head><meta charset='utf-8'>"
13                "<meta name='viewport' content='width=device-width, initial-scale=1'>"
14                "<title>Pico W</title></head>"
15                "<body style='font-family:sans-serif;text-align:center'><h1>Pico W LED</h1>";
16  html += "<p>Durum / State: <b>";
17  html += ledOn ? "AÇIK / ON" : "KAPALI / OFF";
18  html += "</b></p><p><a href='/on'><button>AÇ / ON</button></a> ";
19  html += "<a href='/off'><button>KAPAT / OFF</button></a></p>";
20  html += "<p>Sıcaklık / Temp: ";
21  html += String(analogReadTemp(), 1);   // dahili sensör / internal sensor
22  html += " °C</p></body></html>";
23  server.send(200, "text/html", html);
24}
25
26void setup() {
27  Serial.begin(115200);
28  pinMode(LED_BUILTIN, OUTPUT);          // Pico W'de kablosuz yongadaki LED / the LED on the wireless chip
29  WiFi.mode(WIFI_STA);
30  WiFi.begin(ssid, password);
31  while (WiFi.status() != WL_CONNECTED) {
32    delay(500);
33    Serial.print(".");
34  }
35  Serial.println();
36  Serial.print("IP: ");
37  Serial.println(WiFi.localIP());        // bu adresi telefonda aç / open this address on your phone
38
39  MDNS.begin("picow");                   // http://picow.local
40  server.on("/", sendPage);
41  server.on("/on", []() { ledOn = true;  digitalWrite(LED_BUILTIN, HIGH); sendPage(); });
42  server.on("/off", []() { ledOn = false; digitalWrite(LED_BUILTIN, LOW);  sendPage(); });
43  server.onNotFound([]() { server.send(404, "text/plain", "404"); });
44  server.begin();
45}
46
47void loop() {
48  server.handleClient();
49  MDNS.update();
50}

このスケッチはMicroPythonのLEDページと同じことを行い、さらにanalogReadTemp()で読み取った内部温度を表示します。Wi-Fiドライバはコンパイル後のプログラムに約220 KBを追加するため、最初のビルドには少し時間がかかることがあります。Pico WではLED_BUILTINが無線チップにつながったLEDを自動的に操作し、MDNS.begin("picow")の行のおかげで、ページにはhttp://picow.localからもアクセスできます。MicroPythonとArduinoのどちらにするか迷ったら、次の点を覚えておいてください。MicroPythonではコードを保存してすぐに試せます。Arduinoではコンパイルを待つ必要がありますが、より高速なコードと幅広いライブラリのサポートが得られます。

Raspberry Pi Pico Wのプロジェクトアイデア

  • Wi-Fi温度計:DHT11またはDHT22で測った温度と湿度を、スマホで確認します。
  • スマホで操作するランプ:この記事のLEDページにリレーモジュールを追加して、デスクライトをオン/オフします。
  • 植物の水分モニター:土壌水分センサーの値をページに表示し、土が乾いたら知らせます。
  • 人感アラート:PIRセンサーが人を検知したら、urequestsでスマホにTelegramのメッセージを送ります。
  • インターネット時計:NTPの時刻を、日付と曜日とともにOLEDディスプレイに表示します。
  • ホームオートメーション:mipでumqtt.simpleライブラリをインストールし、測定値をMQTTでHome Assistantに送ります。考え方はESP32のMQTTで説明しています。
  • リアルタイムダッシュボード:より速く更新されるページには、WebSocketを使ったESP32の非同期Webサーバーの記事のアイデアを、Pico W向けに応用します。

よくある間違いと対処法

症状またはエラー原因対処法
エラー:ImportError: no module named 'network'(networkモジュールがない)Pico Wに無印のPico用ファームウェアを書き込んだRPI_PICO_WのUF2ファイルを書き込む(Pico 2 WならRPI_PICO2_W)
wlan.status()が-2のままネットワークが見えない:5 GHz、ネットワーク名の誤り、または信号が弱い2.4 GHz帯をオンにし、wlan.scan()でネットワークを探し、ボードをルーターに近づける
wlan.status()が-3を返すパスワードが違う大文字・小文字とスペースに注意して、パスワードを入力し直す
エラー:OSError: [Errno 98] EADDRINUSE(アドレス使用中)前回のサーバーのソケットが、まだポート80をつかんでいるSO_REUSEADDRを使い、Ctrl+Dで再起動する
スマホでページが開かないスマホがモバイルデータ通信やゲストネットワークにつながっている、またはルーターのクライアント分離(プライバシーセパレーター)がオンスマホを同じWi-Fiに接続し、アドレスはhttpsではなくhttpで入力する
エラー:OSError: -2(urequests)ホスト名を解決できない:Wi-Fiが未接続、またはDNSの問題先にwifi.connect()を呼び、URLを確認する
エラー:OSError: [Errno 12] ENOMEM(メモリ不足)メモリ不足response.close()とgc.collect()を使い、小さな応答を要求する
数時間後に接続が切れる省電力機能、またはルーターの再起動pm=network.WLAN.PM_NONEを使い、ループ内でwlan.isconnected()を確認して再接続する
パソコンから外すと動かないコードがmain.pyとして保存されていない、またはwifi.pyがPicoにない両方のファイルをPicoに保存する
この記事のサーバーにはパスワードもHTTPSもなく、同じネットワーク上の人なら誰でもページを開けます。家のネットワークで試すにはそれで十分ですが、ルーターのポート転送でPico Wを直接インターネットに公開しないでください。外からアクセスする必要があるなら、MQTTやTelegramのような仲介サービスを使うほうがはるかに安全です。また、コードを共有するときは、wifi.pyからWi-Fiのパスワードを削除するのを忘れないでください。

このように、Raspberry Pi Pico WをWi-Fiに接続し、Webページを配信し、インターネットからデータを取得するのに必要なファイルはほんのいくつかです。wifi.pyが接続を担当し、main.pyがプロジェクト本体を動かします。Picoの基礎を固めたいなら、BlueGraysアプリにはRaspberry Pi PicoでのLEDの点滅、信号機、PWM、RGB LEDのプロジェクトがあり、ESP32側にはWi-Fi接続、非同期Webサーバー、NTP時計、天気APIのプロジェクトがあります。どれも回路図とコード付きで、ステップごとに解説しています。

よくある質問

Raspberry Pi Pico Wは5 GHzのWi-Fiに対応していますか?

いいえ。Pico WとPico 2 WのCYW43439チップは2.4 GHz帯でのみ動作し、5 GHzのネットワークは見えません。ルーターの2.4 GHz帯がオンになっていることを確認してください。2つの帯域が同じ名前でうまくいかない場合は、2.4 GHz帯に別の名前を付けると解決します。

Pico WにMicroPythonをインストールするには?

micropython.orgからRPI_PICO_Wボード用の.uf2ファイルをダウンロードします。BOOTSELボタンを押したままPico Wをパソコンに接続し、表示されたRPI-RP2ドライブにファイルをドラッグします。ボードが再起動し、Thonnyで使えるようになります。Thonnyの「MicroPythonをインストールまたは更新」ウィンドウでも同じことができます。

Pico WのIPアドレスを調べるには?

接続後、wlan.ifconfig()[0]でIPアドレスがわかります。この記事のwifi.pyは、アドレスをシェルに表示します。ボードがパソコンにつながっていないときは、ルーターの設定画面にある接続中のデバイス一覧を見るか、mDNSに対応したデバイスでhttp://picow.localを試してください。

Pico Wの内蔵LEDがPin(25)で点灯しないのはなぜですか?

Pico Wの内蔵LEDはプロセッサではなく無線チップにつながっていて、GP25はそのチップのチップセレクト線として使われています。MicroPythonではPin("LED", Pin.OUT)、Arduino IDEではLED_BUILTINを使えば、LEDは正しく点灯します。この書き方は無印のPicoでも動作するので、いつでも使えます。

Pico WのWebサーバーにインターネットからアクセスできますか?

ルーターのポート転送を使えば技術的には可能ですが、おすすめしません。シンプルなMicroPythonのサーバーには、暗号化も認証もないからです。遠隔操作には、MQTT、Telegramボット、クラウドのIoTサービスなどの仲介手段を使うほうがはるかに安全です。

Wi-FiプロジェクトにはPico WとESP32のどちらがいいですか?

どちらも2.4 GHzのWi-FiとBluetoothを備えています。整理されたドキュメントと予想外の挙動がないピンでMicroPythonを使いたいならPico W、より高い処理速度、多くのアナログ入力、幅広いArduinoライブラリのサポートが欲しいならESP32が向いています。シンプルなWebサーバー、センサー、APIのプロジェクトなら、どちらでも十分にこなせます。

全プロジェクトを見る
Raspberry Pi Picoの内蔵LEDを点滅(PicoとPico W)
#147初級

Raspberry Pi Picoの内蔵LEDを点滅(PicoとPico W)

Raspberry Pi Picoシリーズの最初のプロジェクトです!ボード上の緑色LEDを点滅させて動作を確認する、定番の「Hello World」に相当する実験です。外付け部品は不要で、PicoとUSBケーブルだけで始められます。コードはMicroPython(Raspberry Pi Foundationが公式にサポートする言語)で書きます。Thonny IDEでmachine.Pinクラス、Pin.OUTモード、led.on()/off()メソッド、time.sleep()を学びます。シリアル(REPL)出力で、切り替わる様子をリアルタイムに確認できます。

5.0(1)
10
Raspberry Pi Picoで外付けLEDを点灯(ブレッドボード)
#148初級

Raspberry Pi Picoで外付けLEDを点灯(ブレッドボード)

Picoの内蔵LEDの次は、ブレッドボードに外付けLEDを接続し、GP14ピンで制御します。このプロジェクトでは、Picoの汎用GPIOピンを出力として使う方法、LEDの極性(長い足=アノード/+、短い足=カソード/−)、電流制限抵抗(220Ω)が必要な理由を学びます。抵抗を入れないと、LEDに過大な電流が流れて壊れることがあります。回路はとても簡単です。Pico GP14 → 220Ω抵抗 → LED(+)→ LED(−)→ Pico GND。Wokwiでシミュレーションできます。

4.5(2)
7
Raspberry Pi Picoで信号機(3個のLEDを順に点灯)
#150初級

Raspberry Pi Picoで信号機(3個のLEDを順に点灯)

赤・黄・緑のLEDで本物の信号機を再現する、Picoシリーズ初の3個のLEDを使うプロジェクトです!緑が4秒間「進め」、黄が1.5秒間「準備」、赤が3秒間「止まれ」、赤と黄が1秒間「発進準備」を示します(ヨーロッパの信号機のパターン)。関数の定義(def all_off())、状態機械の仕組み(順序)、複数のGPIOの同期を学びます。回路はとても簡単です。GP11(緑)、GP12(黄)、GP13(赤)に3個のLEDを接続し、それぞれに220Ωの電流制限抵抗を使います。

4.0(1)
12
Raspberry Pi PicoのPWMでLEDをフェード(呼吸のような明滅)
#160初級

Raspberry Pi PicoのPWMでLEDをフェード(呼吸のような明滅)

LEDを単に点滅させるだけでなく、呼吸するように明滅させます!Raspberry Pi PicoのPWM(パルス幅変調)を使い、スリープ中のMacBookのランプのように、明るさをゆっくり上げてからゆっくり下げます。machine.PWMクラス、duty_u16()(0–65535の16ビットの明るさ)、PWM周波数(freq(1000)で1秒間に1000回オン・オフし、ちらつきを感じさせない)、range()による段階的なループ(増加と減少)、デジタルピンからのアナログのような出力を学びます。PWMはRGBの混色やモーターなどに使われる、組み込み開発の基本技術です。

0.0(0)
4
ESP32非同期Webサーバー(LCDにIPアドレスを表示)
#129上級

ESP32非同期Webサーバー(LCDにIPアドレスを表示)

このプロジェクトでは、ESP32を同じWi-Fiネットワーク上のスマートフォンやパソコンから操作できる非同期Webサーバーにします。ESPAsyncWebServerライブラリを使うと、従来のWebServer.hのように1つのリクエストで処理がブロックされず、複数のリクエストを同時に処理できます。ブラウザーでESP32のIPアドレスを開き、ON/OFFボタン(/onと/offのルート)を押すと、GPIO 5に接続したLEDを点灯・消灯できます。

4.8(16)
26
ESP32 Wi-Fi STA接続(LCDディスプレイ付き)
#127上級

ESP32 Wi-Fi STA接続(LCDディスプレイ付き)

このプロジェクトでは、ESP32をステーション(STA)モードで家庭のWi-Fiネットワークに接続し、安全で障害に強い接続を実現します。SSIDとパスワードを別のsecrets.hファイルに保存してGitへの流出を防ぎ、自動再接続の仕組みを実装します。RSSIによる信号強度とIPアドレスはI2C LCDディスプレイに表示します。内蔵LEDは状態表示に使います。実運用を想定した接続管理の仕組みです。

4.4(21)
34
ESP32とNTPでインターネットから正確な時刻を取得
#128上級
プレミアム

ESP32とNTPでインターネットから正確な時刻を取得

ESP32をWi-Fiに接続し、NTP(Network Time Protocol)サーバーからミリ秒精度の現在時刻を取得します。configTzTime()関数でタイムゾーンを自動適用し、夏時間を採用する国ではPOSIX TZ文字列で夏時間と標準時間の切り替えも自動管理します。時刻はシリアルモニタと、必要に応じて接続するI2C LCDに表示します。configTzTime()がSNTPサービスを開始し、ネットワークにアクセスできる間は定期的に再同期します。

4.5(13)
21
ESP32 HTTPClient+天気API(LCD付き)
#133上級
プレミアム

ESP32 HTTPClient+天気API(LCD付き)

このプロジェクトでは、ESP32でHTTPClient.hを使い、インターネット経由で天気API(wttr.in)をREST APIとして呼び出します。返されたJSONレスポンスをArduinoJson v7で解析し、都市名、気温、湿度、天気、風の情報をI2C LCDディスプレイにトルコ語と英語で表示します。wttr.inは無料で、APIキーも不要です。都市名を入力するだけで、その都市の現在の天気を取得できます。30分ごとに自動更新し、表示画面ごとに異なるLEDの光り方で状態を知らせます。

4.7(13)
20