Raspberry Pi PicoW のWi-Fi無線通信機能を使用して、パソコンやスマホのブラウザから遠隔操作、データ表示する方法を詳しく紹介します。
今回はプログラム言語は「MicroPython」開発環境は「Thonny」を使用します。
一般的に公開されているWi-Fi遠隔操作、データ表示のチュートリアルを使用して、コピペによる動作確認とWi-Fi接続の詳細な説明から、より実用的な使用方法(表示データ自動更新)まで紹介します。
動作確認に使用するブラウザページ(html、JavaScript、CSS)は「ChatGPT」にお任せして、「MicroPython」を使用したWi-Fi無線通信について理解して、自由に使いこなせるようになりましょう。
「CircuitPython」や「C言語(Arduinoコマンド)」を使用した。PicoWの使用方法は、以下のリンクで詳しく紹介しています。
「ラズパイPico W」の基本仕様、開発環境については以下のリンクで詳しく紹介しています。
1.ラズパイPico Wとは
2.開発環境について
・MicroPythonとは
・Thonnyとは
3.Wi-Fi接続「基本編」
・動作紹介
・サンプルプログラム(コピペ)
・プログラムの詳細(Wi-Fi接続手順)
4.Wi-Fi接続「応用編」
・動作紹介
・サンプルプログラム(コピペ)
・プログラムの詳細(Webページ自動更新)
5.動作確認用のWebページの作成はChatGPTにおまかせ
6.本体に別ファイルで保存したWebページを読み込む方法
7.まとめ
1.ラズパイPico Wとは
「Raspberry Pi PicoW」とは、イギリスを拠点とする慈善団体によって若者のプログラミング学習を目的に開発されたもので、他の「Raspberry Piシリーズ」とは異なり、OSの機能はありませんが、電源投入ですぐに使用でき、スイッチやLEDランプ、モーター、各種センサー、通信機器を接続して、それらを制御するプログラムを簡単に作成して動作確認できるため、組み込み(制御)系プログラミングの学習に最適です。
「Pico W」にはWi-Fi機能があり、Wi-Fi通信を利用したインターネット経由のデータ収集や遠隔操作等のアイデアを簡単に試すことができます。
端子配列は下画像のようになります(公式サイトより抜粋)
2.開発環境について
・MicroPythonとは
「MicroPython」とはマイコンボード(マイクロコントローラ)上での動作に最適化された「Python」と互換性のあるプログラミング言語です。
「Python」とほぼ同じように機能しますが、いくつかの制限があり「Python」の標準ライブラリの一部はサポートされていません。しかし、文法や構文は「Python」と同じため、理解しやすく、マイコンボード上で動作する「Python」によって、「IoT(Internet of Things)」デバイスや組み込みシステムの開発が容易になります。
マイコンボードを使用して、LED、温度センサー、モーター、Wi-Fiモジュールなどを動作させながら「Python」の学習ができるため、初心者にとっても有用です。
・Thonnyとは
「Thonny」とは「ラズベリーパイ」の上位機種にも標準でインストールされている「Python」のプログラム作成、デバッグ、実行を行うことがでる統合開発環境(IDE)です。
簡単なデバッグ機能やエラー情報の確認ができるため、学習におけるストレスを最小限に抑えることができ「Python」プログラマーにとって非常に役立つ開発環境と言えます。
エディタとしての機能は物足りないですが、手軽に導入できて特に初心者の方がお試しするには最適のツールと思います。
「MicroPython」を使用した開発環境「Thonny」の使用方法は以下のリンクで詳しく紹介しています。
3.Wi-Fi接続「基本編」
・動作紹介
「Raspberry Pi PicoW」のWi-Fi通信機能の基本的な使用方法は、英国の慈善団体「Raspberry Pi Foundation」の公式HP(以下リンク先)で詳しく紹介されています。
リンク先の手順通りに進めれば、Wi-Fi通信の手順確認から動作確認まで進める事ができますが、英文での解説で、プログラムも部分的に書かれているだけのため、全体のプログラムをサンプルプログラムとして以下にまとめました。
このサンプルプログラムを使用して各動作について詳しく紹介します。
サンプルプログラムを開発環境「Thonny」にコピペで貼り付けて実行すると以下のように「シェル」部に「PicoW」の「IPアドレス(下画像では「192.168.0.9」)」が表示されます。
パソコンやスマホのブラウザのアドレスバーに「IPアドレス(ここでは192.168.0.9)」を入力すると、下画像のような画面が表示されます。
表示画面の「Ligt on/Light off」ボタンで「PicoW」本体のLEDをON/OFFさせることができます。
その下にはLEDのON/OFF状態が表示され、その下にはPicoW本体の温度センサの値が表示されます。
・サンプルプログラム(コピペ)
サンプルプログラムは以下になります。コピペで貼り付けて実行してください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでもコピーできます。
import network # ネットワーク処理用ライブラリ
import socket # ソケット処理用ライブラリ
from time import sleep # スリープ処理用ライブラリ
from picozero import pico_temp_sensor, pico_led # GPIO制御用ライブラリ
import machine # マイクロコントローラー制御用ライブラリ
# ネットワーク(Wi-Fiルーター)の SSID とパスワードを設定
ssid = '自宅のネットワーク環境のSSIDを設定'
password = '自宅のネットワーク環境のパスワードを設定'
# Wi-Fi 接続実行関数
def connect():
wlan = network.WLAN(network.STA_IF) # WLANオブジェクトを作成
wlan.active(True) # WLANインタフェースを有効化
wlan.connect(ssid, password) # 指定されたSSIDとパスワードでWi-Fiに接続する
while wlan.isconnected() == False: # Wi-Fi接続が確立されるまで待機
print('Waiting for connection...')
sleep(1)
print(wlan.ifconfig()) # Wi-Fi接続情報を全て出力
ip = wlan.ifconfig()[0] # IPアドレスのみを取得
print(f'Connected IP: {ip}') # IPアドレスを出力
return ip # IPアドレスを返す
# ソケットを開く関数
def open_socket(ip):
address = (ip, 80) # IPアドレスとポート番号のタプルを作成
connection = socket.socket() # ソケットオブジェクトを作成
connection.bind(address) # IPアドレスとポート番号をバインド
connection.listen(1) # 接続待機
print(connection) # ソケットオブジェクトを出力
return connection # ソケットオブジェクトを返す
# Webページを準備
def webpage(temperature, state):
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)
# HTTPリクエストを処理する関数
def serve(connection):
# 初期状態を設定
state = 'OFF'
pico_led.off()
temperature = 0
# 接続待機とHTTPリクエスト処理
while True:
client = connection.accept()[0] # クライアントからの接続要求を受け入れる
request = client.recv(1024) # クライアントからのHTTPリクエストを受信する
request = str(request) # バイト文字列を文字列に変換する
try:
request = request.split()[1] # HTTPリクエストの2番目の要素(URL)を抽出する
except IndexError:
pass
# URLによってLEDを制御する
if request == '/lighton?': # 抽出したURLが「/lighton?」なら
pico_led.on() # 本体LEDをオンにする
state = 'ON' # 本体LED状態を'ON'に設定
elif request =='/lightoff?': # 抽出したURLが「/lightoff?」なら
pico_led.off() # 本体LEDをオフにする
state = 'OFF' # 本体LED状態を'OFF'に設定
# 本体温度を取得し、Webページを生成してクライアントに送信する
temperature = pico_temp_sensor.temp # 温度センサーから温度データを取得する
html = webpage(temperature, state) # 温度値とステータスをHTMLページに埋め込む
client.send(html) # クライアントにHTMLページを送信する
client.close() # クライアント接続を閉じる
# メイン処理
try:
ip = connect() # Wi-Fiに接続し、IPアドレスを取得する
connection = open_socket(ip) # IPアドレスを使用してソケットを開く
serve(connection) # HTTPリクエストを処理する
except KeyboardInterrupt:
machine.reset() # 停止(Ctrl-C)が押されたときにPicoを再起動する
・プログラムの詳細(Wi-Fi接続手順)
「基本編」のプログラムの詳細は以下のようになります。
以下リンクの「Raspberry Pi Foundation」のチュートリアルを参考に、Wi-Fi接続の手順からページの読み込みまでサンプルプログラムから抜粋して詳しく紹介します。
1.必要なライブラリ、パッケージの準備
まずは必要なライブラリとパッケージを準備します。
WiFi接続や「PicoW」本体の制御にに必要なライブラリを以下のように準備します。
import network # ネットワーク処理用ライブラリ
import socket # ソケット処理用ライブラリ
from time import sleep # スリープ処理用ライブラリ
from picozero import pico_temp_sensor, pico_led # GPIO制御用ライブラリ
import machine # マイクロコントローラー制御用ライブラリ
「Tonny」を使用した「MicroPython」のパッケージのインストール方法は以下のリンクを参考にしてください。
2.ネットワークのSSID、パスワードの設定
次に、自宅のネットワーク環境に合わせて、以下の部分に「SSID」と「パスワード」を設定します。
# ネットワーク(Wi-Fiルーター)の SSID とパスワードを設定
ssid = '自宅のネットワーク環境のSSIDを設定'
password = '自宅のネットワーク環境のパスワードを設定'
3.Wi-Fi接続実行、IPアドレスの取得
Wi-Fi接続を行うには以下の関数のような手順で行います。
先に設定した「SSID」と「パスワード」を使用して、Wi-Fiネットワークへの接続が開始され、接続が完了すると「IPアドレス」が割り当てられます。
ここではWi-Fiネットワークへの接続と「IPアドレス」の取得を行っています。
# Wi-Fi 接続実行関数
def connect():
wlan = network.WLAN(network.STA_IF) # WLANオブジェクトを作成
wlan.active(True) # WLANインタフェースを有効化
wlan.connect(ssid, password) # 指定されたSSIDとパスワードでWi-Fiに接続する
while wlan.isconnected() == False: # Wi-Fi接続が確立されるまで待機
print('Waiting for connection...')
sleep(1)
print(wlan.ifconfig()) # Wi-Fi接続情報を全て出力
ip = wlan.ifconfig()[0] # IPアドレスのみを取得
print(f'Connected IP: {ip}') # IPアドレスを出力
return ip # IPアドレスを返す
4.ソケットを開く
「ソケット」とは、サーバーがクライアントに応答する方法で、Webページの表示にも利用されます。
今回はサーバー側が「Raspberry Pi PicoW」でクライアント側はスマホやパソコン等の「ブラウザ」になります。
「ソケット」は「IPアドレス」と「ポート番号」を指定して開く必要があり「ポート番号」はサーバーの識別に使用されます。
「ポート番号」は「IPアドレス」に続けて何も指定しなければ通常ポート「80」が使われます。
以下の関数を実行することで「PicoW」 が「ポート80」 で「IPアドレス」への接続に応答するようになります。
これで、接続された「ブラウザ」でWebページを表示する(HTMLコードの提供を開始)準備ができます。
# ソケットを開く関数
def open_socket(ip):
address = (ip, 80) # IPアドレスとポート番号のタプルを作成
connection = socket.socket() # ソケットオブジェクトを作成
connection.bind(address) # IPアドレスとポート番号をバインド
connection.listen(1) # 接続待機
print(connection) # ソケットオブジェクトを出力
return connection # ソケットオブジェクトを返す
5.Webページの準備
以下の関数ではブラウザで表示する Webページを作成します。
Webページは「htmlコード」を文字列として変数に格納して準備しておきます。
文字列を指定する時には「’’’(三重引用符)」を使用します。
複数行の文字列を「’’’」で挟んだ間に記入することでテキスト全体を1つの文字列として扱うことができます。
さらに「f’’’」のように「f」をつけることで文字列内に「変数」を埋め込むこともできます。
この場合の変数は「中括弧{ }」で囲んで指定します。
以下のコードでは「{state}」と「{temperature}」で使用しており、Webページが呼び出されるたびに、その時の変数の状態を渡して表示させることができます。
# Webページを準備
def webpage(temperature, state):
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)
6.HTTPリクエストの応答処理
最後に以下の関数を実行することで、サーバー側でクライアント側からのリクエストを常に待ち受ける、接続待機状態となります。
リクエストがあった時には、リクエストに応じた処理を実行し、文字列として格納したWebページを提供します。
# HTTPリクエストを処理する関数
def serve(connection):
# 初期状態を設定
state = 'OFF'
pico_led.off()
temperature = 0
# 接続待機とHTTPリクエスト処理
while True:
client = connection.accept()[0] # クライアントからの接続要求を受け入れる
request = client.recv(1024) # クライアントからのHTTPリクエストを受信する
request = str(request) # バイト文字列を文字列に変換する
try:
request = request.split()[1] # HTTPリクエストの2番目の要素(URL)を抽出する
except IndexError:
pass
# URLによってLEDを制御する
if request == '/lighton?': # 抽出したURLが「/lighton?」なら
pico_led.on() # 本体LEDをオンにする
state = 'ON' # 本体LED状態を'ON'に設定
elif request =='/lightoff?': # 抽出したURLが「/lightoff?」なら
pico_led.off() # 本体LEDをオフにする
state = 'OFF' # 本体LED状態を'OFF'に設定
# 本体温度を取得し、Webページを生成してクライアントに送信する
temperature = pico_temp_sensor.temp # 温度センサーから温度データを取得する
html = webpage(temperature, state) # 温度値とステータスをHTMLページに埋め込む
client.send(html) # クライアントにHTMLページを送信する
client.close() # クライアント接続を閉じる
ブラウザから「IPアドレス(ルート)」にアクセスすると、文字列として格納しておいたWebページが読み込まれ、以下のように表示されます。
ブラウザ上にはボタンが2つあり、ボタンを押すとそれぞれ以下のような動作になります。
- [Light on]ボタン:「IPアドレス/lighton?」でサーバーにリクエストを送信し、サーバーがこれを受信した場合は本体LEDを点灯させ、LEDの状態変数「state」を「’ON’」にします。
- [Light off]ボタン:「IPアドレス/lightoff?」でサーバーにリクエストを送信し、サーバーがこれを受信した場合は本体LEDを消灯させ、LEDの状態変数「state」を「’OFF’」にします。
7.メイン処理
メインの処理は以下になります。
「connect()」関数でW-Fi接続を実行し「IPアドレス」を取得します。
取得した「IPアドレス」で「ソケット」を開き、リクエストに応答する準備をします。
「serve(connection)」関数を実行することで待機状態となり、受信したリクエストに応じて処理を実行できるようになります。
# メイン処理
try:
ip = connect() # Wi-Fiに接続し、IPアドレスを取得する
connection = open_socket(ip) # IPアドレスを使用してソケットを開く
serve(connection) # HTTPリクエストを処理する
except KeyboardInterrupt:
machine.reset() # 停止(Ctrl-C)が押されたときにPicoを再起動する
4.Wi-Fi接続「応用編」
先に紹介した「基本編」ではWebページの表示データの値は、ページを更新しないと変更されません。
定期的にブラウザページを自動更新するようにしても良いですが、頻繁にページを再読み込みするのは効率的ではありません。
そこで「応用編」ではWebページを再読み込みせずに、表示データの値を定期的に更新して表示させる方法について、詳しく紹介します。
・動作紹介
動作については「基本編」と同じように、この下のサンプルプログラムを「Thonny」にコピペして実行すると、シェル部に「IPアドレス(下画像では192.168.0.9)」が表示されます。
この「IPアドレス」をブラウザのアドレスバーに入力すると、以下のようなページが表示されます。
・サンプルプログラム(コピペ)
サンプルプログラムは以下になります。コピペで貼り付けて実行してください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでもコピーできます。
import network # ネットワーク処理用ライブラリ
import socket # ソケット処理用ライブラリ
from time import sleep # スリープ処理用ライブラリ
from picozero import pico_temp_sensor, pico_led # GPIO制御用ライブラリ
import machine # マイクロコントローラー制御用ライブラリ
# ネットワーク(Wi-Fiルーター)の SSID とパスワードを設定
ssid = '自宅のネットワーク環境のSSIDを設定'
password = '自宅のネットワーク環境のパスワードを設定'
# Wi-Fi 接続実行関数
def connect():
wlan = network.WLAN(network.STA_IF) # WLANオブジェクトを作成
wlan.active(True) # WLANインタフェースを有効化
wlan.connect(ssid, password) # 指定されたSSIDとパスワードでWi-Fiに接続する
while wlan.isconnected() == False: # Wi-Fi接続が確立されるまで待機
print('Waiting for connection...')
sleep(1)
print(wlan.ifconfig()) # Wi-Fi 接続情報を全て出力(IPアドレス、サブネットマスク、 ゲートウェイ、DNS)
ip = wlan.ifconfig()[0] # IPアドレスのみを取得
print(f'Connected IP: {ip}') # IPアドレスを出力
return ip # IPアドレスを返す
# ソケットを開く関数
def open_socket(ip):
address = (ip, 80) # IPアドレスとポート番号のタプルを作成
connection = socket.socket() # ソケットオブジェクトを作成
connection.bind(address) # IPアドレスとポート番号をバインド
connection.listen(1) # 接続待機
print(connection) # ソケットオブジェクトを出力
return connection # ソケットオブジェクトを返す
# Webページを準備
def webpage():
html = '''
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>PicoW Controller</title>
<style>
#container {display: flex;justify-content: center;align-items: center;flex-direction: column;margin-top: 50px;}
h1 {color: #666;}
.button {width: 100px;height: 50px;border-radius: 5px;margin: 10px;cursor: pointer;font-size: 20px;font-weight: bold;color: white;text-align: center;line-height: 50px;}
#on-button {background-color: green;}
#off-button {background-color: red;}
input[type="text"] {width: 200px;padding: 5px;font-size: 20px;text-align: center;margin: 20px;}
</style>
</head>
<body>
<div id="container">
<h1>PicoW Controller</h1>
<hr>
<div>
<button id="on-button" class="button">ON</button>
<button id="off-button" class="button">OFF</button>
</div>
<div><p id="led_state"> </p></div>
<input type="text" placeholder="Temperature" />
</div>
<script>
const onButton = document.getElementById("on-button");
const offButton = document.getElementById("off-button");
const ledState = document.getElementById("led_state");
const textBox = document.querySelector("input[type='text']");
onButton.addEventListener("click", async () => {
const response = await fetch("/lighton");
const text = await response.text();
ledState.textContent = text;
console.log(text);
});
offButton.addEventListener("click", async () => {
const response = await fetch("/lightoff");
const text = await response.text();
ledState.textContent = text;
console.log(text);
});
setInterval(async () => {
const response = await fetch("/get/data");
const text = await response.text();
textBox.value = "本体温度:" + text;
}, 1000);
</script>
</body>
</html>
'''
return html
# Wi-Fi接続実行
try:
ip = connect() # Wi-Fi接続を実行してIPアドレスを取得
connection = open_socket(ip) # IPアドレスでソケットを開く
html = webpage() # htmlデータ読み込み
except (KeyboardInterrupt, OSError) as e:
machine.reset() # 停止またはOSエラーで本体リセット
# サーバー応答処理
while True:
try:
client = connection.accept()[0] # クライアントからの接続要求を受け入れる
request = client.recv(1024) # クライアントからのHTTPリクエストを受信する
request = str(request) # バイト文字列を文字列に変換する
try: # リクエストが存在しない場合に発生する可能性のあるIndexErrorをキャッチし、例外を無視する
request = request.split()[1] # 受信したデータからURLを抽出
except IndexError:
pass
# ヘッダー情報を返す
client.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
# ルートアクセスでhtmlデータを返す
if request == '/': # 抽出したURLが「/」なら
client.send(html) # Webページを返す
print('Root Access')
# 本体 LED ON
elif request == '/lighton': # 抽出したURLが「/lighton」なら
pico_led.on() # 本体LEDをオンにする
client.send('LED_ON!') # 文字列「LED_ON!」を返す
print('LED_ON!')
# 本体 LED OFF
elif request == '/lightoff': # 抽出したURLが「/lighton」なら
pico_led.off() # 本体LEDをオフにする
client.send('LED_OFF!') # 文字列「LED_OFF!」を返す
print('LED_OFF!')
# 本体温度を取得
elif request == '/get/data':
temperature = pico_temp_sensor.temp # 温度センサーから温度データを取得する
temperature = "{:.1f}".format(temperature) # 温度データを小数点1桁の文字列に変換
client.send(temperature) # 温度データを返す
print('Temp:', temperature)
client.close() # クライアント接続を閉じる
except KeyboardInterrupt:
machine.reset() # 停止(Ctrl-C)が押されたときにPicoを再起動する
except OSError as e:
client.close() # OSエラーで接続を閉じる
print('connection closed')
・プログラムの詳細(Webページ自動更新)
「応用編」のプログラムの詳細は以下のようになります。
Wi-Fiの接続設定は「基本編」とほぼ同じですが「Webページ」が大きく違うのと、「サーバー応答処理」が少し異なります。
1.Wi-Fi接続の準備
Wi-Fi接続の準備は「基本編」と同じように以下の手順で行いますが、ここは「基本編」と全く同じです。
①必要なライブラリ、パッケージの準備
②ネットワークのSSID、パスワードの設定
③Wi-Fi接続実行、IPアドレスの取得
④ソケットを開く
2.Webページの準備
Webページは「基本編」と同じように「htmlコード」を文字列として変数に格納して準備しておきます。(「応用編」は「CSS」と「JavaScript」を含みます。)
「基本編」と違い、文字列を「f」付の「’’’(三重引用符)」で挟むのではなく「f」を付けずに「’’’」だけで挟んで記入します。
これでテキスト全体を1つの純粋な文字列として扱うことができます。
「応用編」のWebページでは以下コードの 30〜55行目のように「JavaScript」の「FetchAPI」を使用してリクエストを送信し、受信したレスポンスデータによってWebページの表示を部分的に更新できるようにしています。
# Webページを準備
def webpage():
html = '''
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>PicoW Controller</title>
<style>
#container {display: flex;justify-content: center;align-items: center;flex-direction: column;margin-top: 50px;}
h1 {color: #666;}
.button {width: 100px;height: 50px;border-radius: 5px;margin: 10px;cursor: pointer;font-size: 20px;font-weight: bold;color: white;text-align: center;line-height: 50px;}
#on-button {background-color: green;}
#off-button {background-color: red;}
input[type="text"] {width: 200px;padding: 5px;font-size: 20px;text-align: center;margin: 20px;}
</style>
</head>
<body>
<div id="container">
<h1>PicoW Controller</h1>
<hr>
<div>
<button id="on-button" class="button">ON</button>
<button id="off-button" class="button">OFF</button>
</div>
<div><p id="led_state"> </p></div>
<input type="text" placeholder="Temperature" />
</div>
<script>
const onButton = document.getElementById("on-button");
const offButton = document.getElementById("off-button");
const ledState = document.getElementById("led_state");
const textBox = document.querySelector("input[type='text']");
onButton.addEventListener("click", async () => {
const response = await fetch("/lighton");
const text = await response.text();
ledState.textContent = text;
console.log(text);
});
offButton.addEventListener("click", async () => {
const response = await fetch("/lightoff");
const text = await response.text();
ledState.textContent = text;
console.log(text);
});
setInterval(async () => {
const response = await fetch("/get/data");
const text = await response.text();
textBox.value = "本体温度:" + text;
}, 1000);
</script>
</body>
</html>
'''
return html
3.Wi-Fi接続の実行
Webページの準備ができたら以下のように「connect()」関数でW-Fi接続を実行し、取得した「IPアドレス」で「ソケット」を開き、Webページを読み込んでおきます。
# Wi-Fi接続実行
try:
ip = connect() # Wi-Fi接続を実行してIPアドレスを取得
connection = open_socket(ip) # IPアドレスでソケットを開く
html = webpage() # htmlデータ読み込み
except (KeyboardInterrupt, OSError) as e:
machine.reset() # 停止またはOSエラーで本体リセット
4.サーバー応答処理
最後に以下を実行することで、サーバー側でクライアント側からのリクエストを常に待ち受ける、接続待機状態となります。
「基本編」と異なるのは、リクエストがある毎に毎回Webページを返すのではなく、ルート(IPアドレス)アクセス時にだけWebページを返すようにしています。
その他のリクエストに対しても、リクエストに応じた処理だけを実行します。
# サーバー応答処理
while True:
try:
client = connection.accept()[0] # クライアントからの接続要求を受け入れる
request = client.recv(1024) # クライアントからのHTTPリクエストを受信する
request = str(request) # バイト文字列を文字列に変換する
try: # リクエストが存在しない場合に発生する可能性のあるIndexErrorをキャッチし、例外を無視する
request = request.split()[1] # 受信したデータからURLを抽出
except IndexError:
pass
# ヘッダー情報を返す
client.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
# ルートアクセスでhtmlデータを返す
if request == '/': # 抽出したURLが「/」なら
client.send(html) # Webページを返す
print('Root Access')
# 本体 LED ON
elif request == '/lighton': # 抽出したURLが「/lighton」なら
pico_led.on() # 本体LEDをオンにする
client.send('LED_ON!') # 文字列「LED_ON!」を返す
print('LED_ON!')
# 本体 LED OFF
elif request == '/lightoff': # 抽出したURLが「/lighton」なら
pico_led.off() # 本体LEDをオフにする
client.send('LED_OFF!') # 文字列「LED_OFF!」を返す
print('LED_OFF!')
# 本体温度を取得
elif request == '/get/data':
temperature = pico_temp_sensor.temp # 温度センサーから温度データを取得する
temperature = "{:.1f}".format(temperature) # 温度データを小数点1桁の文字列に変換
client.send(temperature) # 温度データを返す
print('Temp:', temperature)
client.close() # クライアント接続を閉じる
except KeyboardInterrupt:
machine.reset() # 停止(Ctrl-C)が押されたときにPicoを再起動する
except OSError as e:
client.close() # OSエラーで接続を閉じる
print('connection closed')
ブラウザから「IPアドレス(ルート)」にアクセスすると、文字列(html)として格納しておいたWebページが読み込まれ、以下のように表示されます。
ブラウザ上にはボタンが2つあり、ボタンを押すとそれぞれ以下のような動作になります。
- [ON]ボタン:「IPアドレス/lighton」でサーバーにリクエストを送信し、サーバーがこれを受信した場合は本体LEDを点灯させ、レスポンスとして文字列「LED_ON!」を返します。
- [OFF]ボタン:「IPアドレス/lightoff」でサーバーにリクエストを送信し、サーバーがこれを受信した場合は本体LEDを消灯させ、レスポンスとして文字列「LED_OFF!」を返します。
本体温度の表示は「JavaScript」の「setInterval関数」を使用して、1秒毎にサーバー側へリクエストを送信することで、レスポンスデータを取得して表示を更新します。
5.動作確認用のWebページの作成はChatGPTにおまかせ
「JavaScript」を使用したWebページは「ChatGPT」にお任せしましょう。
質問の仕方によっては1回で目的の動作をするコードを書いてくれます。
今回使用した「応用編」のサンプルプログラムのWebページは以下の質問でほぼ書いてくれるので試してみましょう。
「ChatGPT」への質問は以下のように行いました。
「ChatGPT」の回答は以下のようになりました。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>LEDコントロールパネル</title>
<style>
body {
text-align: center; /* このCSSだけ後から追加 */
}
button {
display: inline-block;
padding: 10px 20px;
font-size: 16px;
border-radius: 5px;
cursor: pointer;
}
button.on {
background-color: green;
color: white;
}
button.off {
background-color: red;
color: white;
}
p#led-state {
margin-top: 20px;
font-size: 18px;
}
</style>
</head>
<body>
<div style="display:flex; justify-content:center;">
<button id="on-button" class="on">ON</button>
<button id="off-button" class="off">OFF</button>
</div>
<p id="led-state"></p>
<div style="margin-top:20px; display:flex; justify-content:center;">
<label for="temperature-box">温度表示:</label>
<input type="text" id="temperature-box">
</div>
<script>
const onButton = document.querySelector('#on-button');
const offButton = document.querySelector('#off-button');
const ledStatePara = document.querySelector('#led-state');
const tempBox = document.querySelector('#temperature-box');
onButton.addEventListener('click', () => {
fetch('/lighton')
.then(response => response.text())
.then(data => {
ledStatePara.textContent = data;
console.log(data);
});
});
offButton.addEventListener('click', () => {
fetch('/lightoff')
.then(response => response.text())
.then(data => {
ledStatePara.textContent = data;
console.log(data);
});
});
setInterval(() => {
fetch('/get/data')
.then(response => response.text())
.then(data => {
tempBox.value = data;
});
}, 1000);
</script>
</body>
</html>
以上のような回答になりました。
このコードを「応用編」のサンプルプログラムの「webpage()関数」の中の「html = ”'” 〜 “””」の中で指定した文字列と置き換えて実行すると、以下のようなWebページが表示されました。
残念ながら「LED_ON!」の表示だけ左端に表示されたため、上コードの中の「 7〜9行目」の「CSS」だけ後から追加しています。
修正したものを実行すると下画像のようになり、思い通りのWebページになり問題なく動作しました。
ChatGPTを使用したプログラミングについては、以下のリンクで紹介しています。
6.本体に別ファイルで保存したWebページを読み込む方法
動作確認用のWebページは「Python」のコードの中に文字列として埋め込みましたが「Raspberry Pi Pico W」本体内に保存しておくことで、別ファイルとして読み込んで表示させることもできます。
別ファイルとして保存しておくと、Webページを個別で動作確認や編集をすることが簡単にできるため、毎回「Python」コードに文字列として埋め込む手間が省けて便利です。
Webページを別ファイルとして保存するには「Thonny」を使用して以下のように行います。
まずは下画像のように[新規ファイル]をクリックすると「<untitled>」タブが表示されます
次に下画像のようにWebページのコードを「<untitled>」内に作成またはコピペして[保存]ボタンをクリックします。
下画像のように、保存場所を聞いてくるので[Raspberry Pi Pico]を選択します。
下画像のようなウインドウが表示されたら「ファイル名」を「index.html」として[OK]ボタンをクリックします。
次に「応用編」のサンプルプログラムの「webpage()関数」を下コードと置き換えてから実行します。
以下のコードが実行されると「PicoW」本体内に保存された「index.html」ファイルが読み込まれて、ファイルの内容が文字列として変数htmlに格納され、これをレスポンスとして返すことで、Webページとして表示させることができるようになります。
# 外部HTMLファイルの読み込み
def webpage():
f = open('/index.html', 'r') # PicoW内に保存されたHTMLファイルを開く
html = f.read() # コンテンツを読み込む
f.close() # ファイルを閉じる
return html
7.まとめ
「Raspberry Pi Pico W」 のWi-Fi無線通信機能を使用して、パソコンやスマホのブラウザから遠隔操作、データ表示する方法を「MicroPython」で詳しく紹介しました。
最初の動作確認は「Raspberry Pi Foundation」の公式HPを参考に行いましたが、Webページの表示内容を更新するにはページの再読み込みが必要だったので、ここでは「基本編」として紹介しました。
実際にはWebページの表示内容は自動で更新されないと実用的ではないため「応用編」として「JavaScript」の「FetchAPI」の非同期通信でデータの送受信を行い、ページを自動更新する方法も紹介しました。
Webページのコードは「Pyhton」コード内に文字列として埋め込むこともできますが、別ファイルとして保存しておいた方が、Webページの動作確認や編集が容易に行えるため、その方法も紹介しました。
動作確認に使用するブラウザページ(html、JavaScript、CSS)は「ChatGPT」にお任せすれば比較的簡単に作成することができるので活用していきましょう。
これで「Raspberry Pi Pico W」のWi-Fi無線機能を「MicroPython」で自由に使えるようになりました。今後はこれを利用したIoTデバイスとして、データ収集や遠隔操作、監視等の方法も紹介していきたいと思います。
「CircuitPython」や「C言語(Arduinoコマンド)」を使用した遠隔操作、データ表示の方法も以下のリンクで詳しく紹介しています。
コメント
RaspPi PicoW の勉強の為、拝見させて頂いています。
応用編のsample2をコピーして、PicoWにmain.pyとして動作させていますが
長時間動作(1日程度)させていると、PICO W(WebServer)に接続できなくなります。
尚、PICO Wを固定IPとする為、15行目に下記を追記しております。
wlan.ifconfig((‘192.168.0.61’, ‘255.255.255.0’, ‘192.168.0.1’, ‘192.168.0.1’))
何かヒントを頂けると助かります。
kyoppeさん初めまして、読んでいただきありがとうございます。
同じように固定IPで動作させてしばらく様子を見てますが、とりあえず接続できています。
こちらも1日放置して様子を見てみます。
接続できない状態でコマンドプロンプト(MACならターミナル)から「ping 192.168.0.61」を入力すると応答は返ってきますでしょうか?
返ってこない場合はラズパイサーバーが何らかの原因で停止してます。
この時ラズパイ自体が生きているかどうかを確認するため、本体LEDを常にフリッカさせる処理を入れて様子をみてみてはいかがでしょうか?
フリッカも止まっていればラズパイ自体が停止しているので、ネットワークというよりもハードの問題か、電源の問題かもしれません。
パソコンと接続して放置した時、シリアルモニタ(Thonnyならシェル)の温度表示がどのタイミングで表示されなくなるでしょう?何かエラーは出てないでしょうか?
ひとまず思いつくのはこの辺りですが、こちらでも同様のエラーが出るようであればもう少し探ってみたいと思います。
早速の返信を頂きながら、返信せずすみませんでした。
先日の質問後、接続できない現象発生していおりません。現在も1週間程、電源入れたまま、1日数回、複数の端末(PC1台、スマホ2台)からアクセスしていますが、正常に接続出来ています。外出先からVPN接続した場合も正常に接続できています。
引き続き、連続動作させて様子を見てみます。
問題発生時は、また質問させて頂きますので、その際は、よろしくお願いいたします。
返信に関してはお気になさらず^^結果ご連絡いただけただけでありがたいですよ♪
あの時はこちらも2日程放置してみたのですが特に問題なく動いていたので、一時的なネットワークのトラブルがあったんでしょうかね。
サンプルプログラムについは実際の運用上の問題には気づかないこともあるので、また何あったら教えてください。