ラズパイPicoW Wi-Fi通信、遠隔操作&表示 CircuitPython編

ラズパイPico W Wi-Fi遠隔操作CircuitPythonアイキャッチ

前回は「MicroPython」を使用したブラウザからの遠隔操作、データ監視の方法を紹介しましたが、今回は「CircuitPython」を使用した方法を詳しく紹介します。

動作確認用のブラウザページ(html、JavaScript、CSS)は今回も「ChatGPT」にお任せして、「CircuitPython」を使用したWi-Fi無線通信について理解し、自由に使いこなせるようになりましょう。

遠隔操作の方法として「サーバー」機能を利用していますが、ここで紹介する「サーバー」機能を使いこなせば「ChatGPT」のようなチャットボットを作ることもできるようになります。
これについても、作り方を以下のリンクで詳しく紹介しています。

ラズパイPicoWでChatGPTのようなチャットボットの作り方
Raspberry Pi PicoWでAPI(gpt-3.5-turbo)を使ったChatGPTのようなチャットボットの作り方をコピペ用サンプルプログラムで詳しく紹介します。


「MicroPython」や「C言語(Arduinoコマンド)」を使用した、Wi-Fi通信、遠隔操作&表示の方法も、以下のリンクで詳しく紹介しています。

ラズパイPicoW Wi-Fi通信、遠隔操作&表示 MicroPython編
Raspberry Pi PicoWのWi-Fi無線通信を使用してMicroPythonでパソコンやスマホのブラウザから遠隔操作,データ表示する方法を詳しく紹介
ラズパイPicoW Wi-Fi通信、遠隔操作&表示 C言語(Arduinoコマンド)編
Wi-Fi通信機能を使用してPicoWをサーバーに設定して遠隔操作&表示する方法を詳しく紹介します。動作確認のブラウザページはChatGPTに全部書いてもらいましょう。

「Raspberry Pi PicoW」の基本仕様、開発環境については以下のリンクで詳しく紹介しています。

ラズパイPico/PicoWの使い方を3つの開発環境Python、ArduinoIDE、PlatformIOで紹介
Raspberry Pi Pico/Pico Wの使い方を端子配列からPython(MicroPython)とC言語の開発環境、Lチカ方法まで紹介。PythonはTonny、C言語はArduinoIDEとPlatformIOの3種類で詳しく紹介します。
スポンサーリンク

1.ラズパイPico Wとは

「Raspberry Pi PicoW」とは、イギリスを拠点とする慈善団体によって若者のプログラミング学習を目的に開発されたもので、他の「Raspberry Piシリーズ」とは異なり、OSの機能はありませんが、電源投入ですぐに使用でき、スイッチやLEDランプ、モーター、各種センサー、通信機器を接続して、それらを制御するプログラムを簡単に作成して動作確認できるため、組み込み(制御)系プログラミングの学習に最適です。

「Pico W」にはWi-Fi機能があり、Wi-Fi通信を利用したインターネット経由のデータ収集や遠隔操作等のアイデアを簡単に試すことができます。

端子配列は下画像のようになります(公式サイトより抜粋

ラズパイPico/Pico Wの端子配列
スポンサーリンク

2.開発環境について

・CircuitPythonとは

「CircuitPython」とはマイコンボード(マイクロコントローラ)上での動作に最適化された「Python3」と互換性のある「MicroPython」から派生した、教育および初心者向けのオープンソースのプログラミング言語です。

「Adafruit社」が開発を支援しており、同社のマイコンボードはもちろん「Raspberry Pi Pico W」でも使用できます。

「CircuitPython」の公式サイトは以下のリンクになります。

CircuitPython
The easiest way to program microcontrollers

・Thonnyとは

「Thonny」とは「ラズベリーパイ」の上位機種にも標準でインストールされている「Python」のプログラム作成、デバッグ、実行を行うことがでる統合開発環境(IDE)です。

簡単なデバッグ機能やエラー情報の確認ができるため、学習におけるストレスを最小限に抑えることができ「Python」プログラマーにとって非常に役立つ開発環境と言えます。

エディタとしての機能は物足りないですが、手軽に導入できて特に初心者の方がお試しするには最適のツールと思います。


「CircuitPython」を使用した開発環境「Thonny」の使用方法は以下のリンクで詳しく紹介しています。

ラズパイPicoの使い方 CircuitPython&開発環境Thonny
Raspberry Pi PicoでCircuitPythonを使ったプログラミング方法を開発環境Thonnyを使用してインストールからライブラリの追加、サンプルプログラム(コピペ)による動作確認(液晶表示SSD1306を例に)まで詳しく紹介します。

・adafruit_httpserverライブラリの新旧の違いについて

ご注意:2023/5/23に「adafruit_httpserver」ライブラリが変更されました。
ここのサンプルプログラムは変更後のライブラリで動作するように書き換えています。
変更前のライブラリとの違いも書いていますので、必要に応じて書き換えて下さい。

最新のライブラリは以下の「Adafruit」社のサイトから「PicoW」にインストールした「CircuitPython」のバージョン(7.x か 8.x)に合わせてダウンロードして下さい。

CircuitPython - Libraries
The easiest way to program microcontrollers
サンプルプログラムは「バージョン8.x」の「adafruit-circuitpython-bundle-8.x-mpy-20230523」の中のものを使用しています。
私が確認した中で「
adafruit-circuitpython-bundle-8.x-mpy-20230516」以前のものでは一部書き換える必要があります。

最新のライブラリの使い方は、以下の「GitHub」で紹介されています。

GitHub - adafruit/Adafruit_CircuitPython_HTTPServer: Simple HTTP Server for CircuitPython
Simple HTTP Server for CircuitPython. Contribute to adafruit/Adafruit_CircuitPython_HTTPServer development by creating an account on GitHub.

「Adafruit社」のライブラリのダウンロードから保存方法は以下のリンクで詳しく紹介しています。

ラズパイPicoの使い方 CircuitPython&開発環境Thonny
Raspberry Pi PicoでCircuitPythonを使ったプログラミング方法を開発環境Thonnyを使用してインストールからライブラリの追加、サンプルプログラム(コピペ)による動作確認(液晶表示SSD1306を例に)まで詳しく紹介します。

「adafruit_httpserver」ライブラリの変更内容は以下になります。

# importの設定方法変更 ----------------------------------------------------------------------
# <変更後ライブラリ(サンプルプログラム)の場合>
from adafruit_httpserver import Server, Request, Response
# ファイルも返す場合は以下のようにする
from adafruit_httpserver import Server, Request, Response, FileResponse

# <変更前ライブラリの場合
from adafruit_httpserver.mime_type import MIMEType    # MIMEタイプ(Webサーバーが送信するファイルの種類)を扱えるようにする
from adafruit_httpserver.request import HTTPRequest   # Webブラウザからのリクエストに関する情報を取得するために使用
from adafruit_httpserver.response import HTTPResponse # Webブラウザに対して送信するレスポンスを設定するために使用
from adafruit_httpserver.server import HTTPServer     # HTTPリクエストを受信し、適切なHTTPレスポンスを返信すために使用

# サーバーの起動方法変更 ----------------------------------------------------------------------
# <変更後ライブラリ(サンプルプログラム)の場合>
pool = socketpool.SocketPool(wifi.radio)      # Wi-Fi接続上でソケットを管理するためのオブジェクトを作成
server = Server(pool, "/static", debug=True)  # サーバー起動時にPicoWのルートディレクトリからデータを提供できるようにする

# <変更前ライブラリの場合>
pool = socketpool.SocketPool(wifi.radio)  # Wi-Fi接続上でソケットを管理するためのオブジェクトを作成
server = HTTPServer(pool, "/static")      # サーバー起動時にPicoWのルートディレクトリからデータを提供できるようにする

# サーバーリクエストに対するレスポンスを「HTML」で送信する方法 ----------------------------------------------------
# <変更後ライブラリ(サンプルプログラム)の場合>
# ルート(IPアドレス:ポート)アクセス時に実行される関数
@server.route("/")
def base(request: Request):
    html = webpage()              # WebページのHTMLコードを取得
    return Response(request, html, content_type="text/html")

# <変更前ライブラリの場合>
# ルート(IPアドレス:ポート)アクセス時に実行される関数
@server.route("/")
def base(request: HTTPRequest):   # HTTPリクエストに応答する関数を定義
    html = webpage()              # WebページのHTMLコードを取得
    with HTTPResponse(request, content_type=MIMEType.TYPE_HTML) as response: # レスポンスを返すためのHTTPオブジェクトを作成
        response.send(html)       # レスポンスとしてHTMLコードを送信

# サーバーリクエストに対するレスポンスを「テキスト(文字列)」で送信する方法 ----------------------------------------------------
# <変更後ライブラリ(サンプルプログラム)の場合>
# 本体LED点灯(IPアドレス:ポート/lighton)アクセス時に実行される関数
@server.route("/lighton")
def light_on(request: Request):             # HTTPリクエストを処理する関数を定義
    led.value = True                        # 本体LEDを点灯
    return Response(request, "LED_ON!", content_type="text/plane")

# <変更前ライブラリの場合>
# 本体LED点灯(IPアドレス:ポート/lighton)アクセス時に実行される関数
@server.route("/lighton")
def light_on(request: HTTPRequest):         # HTTPリクエストを処理する関数を定義
    led.value = True                        # 本体LEDを点灯
    with HTTPResponse(request) as response: # レスポンスを返すためのHTTPオブジェクトを作成(MIMEタイプは指定しなければplane/text)
        response.send("LED_ON!")            # レスポンスとして文字列「LED_ON!」を送信

# サーバーリクエストに対するレスポンスに「PicoW」本体内の「ファイル」を送信する方法 ----------------------
# <変更後ライブラリ(サンプルプログラム)の場合>
# ルート(IPアドレス:ポート)アクセス時に実行される関数
@server.route("/")
def base(request: Request):  # HTTPリクエストを処理する関数を定義
    return FileResponse(request, filename='index.html', root_path='/')

# <変更前ライブラリの場合>
# ルート(IPアドレス:ポート)アクセス時に実行される関数
@server.route("/")
def base(request: HTTPRequest):             # HTTPリクエストを処理する関数を定義
    with HTTPResponse(request, content_type=MIMEType.TYPE_HTML) as response:  # レスポンスを返すためのHTTPオブジェクトを作成
        response.send_file("index.html")    # レスポンスとしてPicoWルートフォルダの「index.html」ファイルを読み込みHTMLデータとして返す
書き方がシンプルになっていいんですけど、新しい投稿を書いている最中の変更だと急遽修正が必要になるのでちょっと大変・・・
今後も変更はあると思うので、できるだけ最新のもので対応していきます。
スポンサーリンク

3.MicroPythonとの違い

「MicroPython」と「CircuitPython」は、両方とも「Python」から派生し、マイクロコントローラー用に最適化されたものです。

「Python」の基本的な文法をサポートしていますが、ライブラリに関しては共通のものは一部で、多くのライブラリは専用のものを使用する必要があります。

どちらも「Python」のわかりやすい文法を使用してマイクロコントローラーをプログラミングすることができるため、初心者から組み込み開発者まで非常に有用なツールです。

主な特徴はそれぞれ以下のようになります。

  • MicroPython
    「Python 3.4」をベースに開発され「Python」標準の関数やモジュールの多くが使えるため、「Python」を使ったことがある人には扱い易いです。
    サポートしているマイクロコントローラーやライブラリは「CircuitPython」よりは少ないですが、それでも初心者の方が扱うには十分な機能があり、マイクロコントローラーを使用して「Python」学習を始めるための有益なツールと思います。
  • CircuitPython
    「MicroPython」から派生し「Adafruit社」によって開発されたもので「Arduino」や「Raspberry Pi」「ESP32」等多くのマイクロコントローラーをサポートしています。
    「MicroPython」から派生したフレームワークで、対応しているマイクロコントローラーやライブラリが多く、より高度な機能を扱うことができます。
    このため「MicroPython」で「Python」言語自体に慣れてきたら「CircuitPython」に移行することで、より高度な制御を体験することができます。

その他にも、使用方法には以下の大きな違いがあります。

・USBメモリのように認識される

「CircuitPython」の開発環境の特徴は、マイコンボードをパソコンと接続した時にUSBメモリのように認識されて、マイコンボード内のデータを操作できることです。

マイクロコントローラー内のデータをフォルダとして開いて操作できるため、メモ帳や他のエディタで作成したプログラムを直接このフォルダ内に置くことで実行できたり、ライブラリや参照データを保存して、これらを利用したプログラムを作成することができます。

「PicoW」に「CircuitPython」のファームウェアを書き込んでパソコンと接続すると「CIRCUITPY」というフォルダが認識されます。これを開くと以下のようなファイルが確認できます。

ラズパイPico W Wi-Fi遠隔操作CircuitPython

ファイルの内容は以下のようになります。

  • boot_out.txt:「CircuitPython」のバージョンやマイクロコントローラーの情報、MACアドレス等の情報が確認できます。
  • code.py:このファイルに「CircuitPython」のプログラムを記入することで実行できます。
  • lib:ライブラリ保存フォルダ。プログラムを実行するために必要なライブラリを保存しておきます。
  • settings.toml:Ver8から導入された、環境変数設定ファイルです。Wi-Fi接続情報(SSID、パスワード)やAPIキー等を設定して、このファイルから読み込むことで、メインのプログラムから分離することができます。

・環境変数設定ファイル「setting.toml」について(Ver.8以降)

環境設定ファイル「setting.toml」は環境変数を設定しておくためのファイルで「CircuitPython」のバージョン8.0.0から導入されました。

詳細は以下リンク先の「Adafruit社」のサイトを参照してください。

Environment Variables — Adafruit CircuitPython 9.1.0-beta.1 documentation

「setting.toml」は「PicoW」のルートフォルダに配置されており、このファイルに変数としてWi-Fi接続情報(SSID、パスワード)等を設定しておくことで、メインのプログラムから読み込んで使用することができます。

今回紹介するサンプルプログラムの「基本編」「応用編」ではこれは使用しません。
詳しい使用方法は「6.本体に別ファイルで保存したWebページを読み込む方法」で詳しく紹介します。
「環境変数」はWindows等パソコンでも使用され、機密情報を直接プログラム内に埋め込まずに、外部変数から参照する事でセキュリティを向上させるために使用されますが、「setting.toml」ファイルは同じ「PicoW」内に配置されているため、プログラムから機密情報が分離されるだけで安全ではありません。

4.「基本編」Wi-Fi接続、遠隔操作&表示

「基本編」はシンプルなWi-Fi通信による、サーバーを使用した遠隔操作とデータ表示の方法を確認します。
開発環境「Thonny」の準備がまだの方は、以下リンクを参照してインストールしてください。

ラズパイPicoの使い方 CircuitPython&開発環境Thonny
Raspberry Pi PicoでCircuitPythonを使ったプログラミング方法を開発環境Thonnyを使用してインストールからライブラリの追加、サンプルプログラム(コピペ)による動作確認(液晶表示SSD1306を例に)まで詳しく紹介します。

・動作紹介

「Thonny」を起動して、この下の「サンプルプログラム」をコピペして実行すると下画像のように「シェル」部にIPアドレスが表示されます。

別途「adafruit_httpserver」ライブラリを「PicoW」本体の「libフォルダ」保存しておく必要があります。ライブラリの保存方法については以下のリンクで詳しく紹介しています。
ラズパイPicoの使い方 CircuitPython&開発環境Thonny
Raspberry Pi PicoでCircuitPythonを使ったプログラミング方法を開発環境Thonnyを使用してインストールからライブラリの追加、サンプルプログラム(コピペ)による動作確認(液晶表示SSD1306を例に)まで詳しく紹介します。
ラズパイPico W Wi-Fi遠隔操作CircuitPython

表示されたIPアドレスをクリックするか、ブラウザのアドレスバーに「IPアドレス」を入力してアクセスすると、Webページが読み込まれ、以下のように表示されます。

IPアドレスの後ろについている「:80」はポート番号で「:80」は省略可能なため、今回の場合は「192.168.0.13」だけでアクセスできます。
プログラム内でポート番号を変更した場合は入力する必要があります。
ラズパイPico W Wi-Fi遠隔操作MicroPython

ブラウザ上にはボタンが2つあり、ボタンを押すとそれぞれ以下のような動作になります。

  • [Light on]ボタン:「IPアドレス/lighton?」でサーバーにリクエストを送信し、サーバーがこれを受信した場合は本体LEDを点灯させ、LEDの状態変数「state」を「’ON’」にします。
  • [Light off]ボタン:「IPアドレス/lightoff?」でサーバーにリクエストを送信し、サーバーがこれを受信した場合は本体LEDを消灯させ、LEDの状態変数「state」を「’OFF’」にします。

「PicoW」へのアクセスが成功して、ブラウザ上の各ボタンを操作すると「シェル」部には下画像のように通信結果が表示されていきます。

ラズパイPico W Wi-Fi遠隔操作CircuitPython

通信結果に表示されている「IPアドレス(画像では192.168.0.8)」はアクセスしてきたクライアントの「IPアドレス」です。
「”GET 〜”」にはクライアントからのリクエストアドレスが表示されます。横の数値はリクエスト時のデータ量です。
「”200 OK”」は通信成功を表します。横の数値はレスポンス時のデータ量です。

「LED is 〜」には本体LEDの状態、「Temperature is 〜」には本体温度が表示されますが、この値はページが更新されないと変わりません。
「応用編」のサンプルプログラムではページを更新せずに、定期的に表示を更新する方法を詳しく紹介しています。

・サンプルプログラム(コピペ)

「基本編」のサンプルプログラムは以下になります。コピペで貼り付けて実行してください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでもコピーできます。

15,16行目の「ssid」と「password」は、自宅のネットワーク環境に合わせて設定してください。
import socketpool       # TCP/IP接続用のソケット管理用
import wifi             # WiFi接続を管理用
import digitalio        # デジタルピンの入出力を制御用
from board import *     # マイクロコントローラーのボード定義から全てのピンをインポート
import microcontroller  # マイクロコントローラーに関する情報(CPU温度等)を取得
from adafruit_httpserver import Server, Request, Response # HTTPサーバー用ライブラリ

led = digitalio.DigitalInOut(LED)           # 本体LEDをGPIO端子に割り当て
led.direction = digitalio.Direction.OUTPUT  # LED端子を出力に設定

# グローバル変数
state = "OFF!"  # 本体LED状態格納用

# Wi-FiアクセスポイントのSSIDとパスワードを設定(直接指定する場合)
ssid = "自宅のネットワーク環境のSSIDを設定"          # Wi-Fi接続先SSID
password = "自宅のネットワーク環境のパスワードを設定"  # Wi-Fi接続先パスワード
port_num = 80               # ポート番号(標準は80)

# Wi-Fi接続を実行
wifi.radio.connect(ssid, password)  # 指定したSSIDとパスワードで接続
print("Connected to", ssid)         # 接続先SSID表示

# ソケットプールを作成してHTTPサーバーを起動するための準備
pool = socketpool.SocketPool(wifi.radio)  # Wi-Fi接続上でソケットを管理するためのオブジェクトを作成
server = Server(pool, "/static", debug=True)      # サーバー起動時にPicoWのルートディレクトリからデータを提供できるようにする

# Webページを準備
def webpage():
    temperature = microcontroller.cpu.temperature # 本体温度センサの温度取得
    temperature = "{:.1f}".format(temperature)    # 温度の値を小数点1桁に成形
    # HTMLコードを文字列として埋め込み、「f""" 〜 """」で囲むと{ }で囲った変数の値を埋め込める
    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 {state}</p>
        <p>Temperature is {temperature}</p>
        </body>
        </html>
        """
    return str(html)  # HTMLコードを文字列として返す

# ルート(IPアドレス:ポート)アクセス時に実行される関数(トップページ表示、ポート80なら省略可)
@server.route("/")
def base(request: Request):
    html = webpage()              # WebページのHTMLコードを取得
    return Response(request, html, content_type="text/html")

# 本体LED点灯(IPアドレス:ポート/lighton)アクセス時に実行される関数(ポート80なら省略可)
@server.route("/lighton")
def light_on(request: Request):   # HTTPリクエストに応答する関数を定義
    global state                  # グローバル変数の使用
    state = "ON!"                 # 文字列「ON!」を格納
    led.value = True              # LEDを点灯
    html = webpage()              # WebページのHTMLコードを取得
    return Response(request, html, content_type="text/html")

# 本体LED消灯(IPアドレス:ポート/lightoff)アクセス時に実行される関数(ポート80なら省略可)
@server.route("/lightoff")
def light_off(request: Request):  # HTTPリクエストに応答する関数を定義
    global state                  # グローバル変数の使用
    state = "OFF!"                # 文字列「ON!」を格納
    led.value = False             # LEDを消灯
    html = webpage()              # WebページのHTMLコードを取得
    return Response(request, html, content_type="text/html")

# HTTPサーバーを開始して待ち受けるIPアドレスを表示する
print(f"Listening on http://{wifi.radio.ipv4_address}:{port_num}")  # f文字列を使って「IPアドレス」と「ポート番号」を埋め込んで表示
server.serve_forever(str(wifi.radio.ipv4_address), port=port_num)   # HTTPサーバーを指定された「IPアドレス」と「ポート」で開始

・プログラムの詳細

Wi-Fi接続方法から、サーバー機能を利用した遠隔操作、データ表示をする方法を、サンプルプログラムから抜粋して詳しく紹介します。
Wi-Fi接続手順まではサンプルプログラムの「基本編」と「応用編」で同じで、以下のようになります。


まずは必要なライブラリをインポートします。
Wi-Fi接続に必要な「socketpool」と「wifi」ライブラリ、「PicoW」本体のLED制御と本体温度測定用の「digitalio」「board」「microcontroller」ライブラリをインポートします。

「httpサーバー」を開始するために必要な「adafruit_httpserverライブラリは、別途「Adafruit社」のホームページからダウンロードして「PicoW」本体の「libフォルダ」に保存しておきます

import socketpool       # TCP/IP接続用のソケット管理用
import wifi             # WiFi接続を管理用
import digitalio        # デジタルピンの入出力を制御用
from board import *     # マイクロコントローラーのボード定義から全てのピンをインポート
import microcontroller  # マイクロコントローラーに関する情報(CPU温度等)を取得
from adafruit_httpserver import Server, Request, Response # HTTPサーバー用ライブラリ

「Adafruit社」のライブラリは以下のリンクからダウンロードできます。
最新のファームウェアをインストールした場合のライブラリはバージョン「8.x」になります。

CircuitPython - Libraries
The easiest way to program microcontrollers

「Adafruit社」のライブラリのダウンロードから保存方法は以下のリンクで詳しく紹介しています。

ラズパイPicoの使い方 CircuitPython&開発環境Thonny
Raspberry Pi PicoでCircuitPythonを使ったプログラミング方法を開発環境Thonnyを使用してインストールからライブラリの追加、サンプルプログラム(コピペ)による動作確認(液晶表示SSD1306を例に)まで詳しく紹介します。

次に「PicoW」本体のLEDを使用できるようにGPIO端子の設定を以下のように行います。

# 本体LED設定
led = digitalio.DigitalInOut(LED)           # 本体LEDをGPIO端子に割り当て
led.direction = digitalio.Direction.OUTPUT  # LED端子を出力に設定

LEDの「ON/OFF」状態を格納するためのグローバル変数を以下のように準備します。

# グローバル変数
state = "OFF!"  # 本体LED状態格納用

Wi-Fi接続を行うためには以下のように設定します。
以下コードの「2,3行目」には、ご自宅のWi-Fi環境の「SSID」と「パスワード」を設定する必要があります。

# Wi-FiアクセスポイントのSSIDとパスワードを設定(直接指定する場合)
ssid = "自宅のネットワーク環境のSSIDを設定"          # Wi-Fi接続先SSID
password = "自宅のネットワーク環境のパスワードを設定"  # Wi-Fi接続先パスワード
port_num = 80               # ポート番号(標準は80)

# Wi-Fi接続を実行
wifi.radio.connect(ssid, password)  # 指定したSSIDとパスワードで接続
print("Connected to", ssid)         # 接続先SSID表示

# ソケットプールを作成してHTTPサーバーを起動するための準備
pool = socketpool.SocketPool(wifi.radio)  # Wi-Fi接続上でソケットを管理するためのオブジェクトを作成
server = Server(pool, "/static", debug=True)      # サーバー起動時にPicoWのルートディレクトリからデータを提供できるようにする

設定した内容でWi-Fi接続を実行し、httpサーバーを起動するための「ソケット」を開きます。

「ソケット」とは、サーバーがクライアントに応答する方法で、Webページの表示にも利用されます。
今回はサーバー側が「Raspberry Pi PicoW」でクライアント側はスマホやパソコン等の「ブラウザ」になります。

以上がWi-Fi接続を行うための手順です。


次にブラウザで表示するWebページを以下のように準備しておきます。
「HTMLコード」を文字列として変数に格納します。この時に文字列を「””” 〜 “””」のように「”””」で囲むと改行を無視して1つの文字列として扱うことができます。

さらに「f “”” 〜 “””」のように「f」をつけることで、文字列内で中括弧{ }で囲った変数の値を文字列として埋め込むことができるようになります。

ここではLEDの「ON/OFF」状態を格納する変数「state」と本体温度を格納する変数「temperature」をページ内に埋め込んでいます。

本体温度は先に取得して文字列に埋め込むようにしています。

# Webページを準備
def webpage():
    temperature = microcontroller.cpu.temperature # 本体温度センサの温度取得
    temperature = "{:.1f}".format(temperature)    # 温度の値を小数点1桁に成形
    # HTMLコードを文字列として埋め込み、「f""" 〜 """」で囲むと{ }で囲った変数の値を埋め込める
    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 {state}</p>
        <p>Temperature is {temperature}</p>
        </body>
        </html>
        """
    return str(html)  # HTMLコードを文字列として返す

次に指定のアドレスにリクエストがあったときに実行する関数を各アドレスごとに以下のように設定します。

# ルート(IPアドレス:ポート)アクセス時に実行される関数(トップページ表示、ポート80なら省略可)
@server.route("/")
def base(request: Request):
    html = webpage()              # WebページのHTMLコードを取得
    return Response(request, html, content_type="text/html")

# 本体LED点灯(IPアドレス:ポート/lighton)アクセス時に実行される関数(ポート80なら省略可)
@server.route("/lighton")
def light_on(request: Request):   # HTTPリクエストに応答する関数を定義
    global state                  # グローバル変数の使用
    state = "ON!"                 # 文字列「ON!」を格納
    led.value = True              # LEDを点灯
    html = webpage()              # WebページのHTMLコードを取得
    return Response(request, html, content_type="text/html")

# 本体LED消灯(IPアドレス:ポート/lightoff)アクセス時に実行される関数(ポート80なら省略可)
@server.route("/lightoff")
def light_off(request: Request):  # HTTPリクエストに応答する関数を定義
    global state                  # グローバル変数の使用
    state = "OFF!"                # 文字列「ON!」を格納
    led.value = False             # LEDを消灯
    html = webpage()              # WebページのHTMLコードを取得
    return Response(request, html, content_type="text/html")


最後に取得された「IPアドレス」と設定した「ポート番号」で「HTTPサーバー」を開始します。
(ポート指定は省略可。この場合はポート80となります。)

# HTTPサーバーを開始して待ち受けるIPアドレスを表示する
print(f"Listening on http://{wifi.radio.ipv4_address}:{port_num}")  # f文字列を使って「IPアドレス」と「ポート番号」を埋め込んで表示
server.serve_forever(str(wifi.radio.ipv4_address), port=port_num)   # HTTPサーバーを指定された「IPアドレス」と「ポート」で開始

以上で「PicoW」が「HTTPサーバー」として機能し、クライアントからのリクエストを常に待ち受ける状態になります。
リクエストがあった場合は設定した処理を実行し、所得したデータで更新された「htmlコード」をレスポンスとして返します。

5.「応用編」Wi-Fi接続、遠隔操作&表示

「基本編」ではデータの表示を更新するためにページの更新をする必要がありました。
「応用編」ではページの更新をせずにデータの更新をすることができます。
これには「JavaScript」の「FetchAPI」を使用します。

・動作紹介

動作については「基本編」と同じように、この下の「サンプルプログラム」を「Thonny」にコピペして実行すると、シェル部に「IPアドレス(下画像では192.168.0.13:80)」が表示されます。

別途「adafruit_httpserver」ライブラリを「PicoW」本体の「libフォルダ」保存しておく必要があります。ライブラリの保存方法については以下のリンクで詳しく紹介しています。
ラズパイPicoの使い方 CircuitPython&開発環境Thonny
Raspberry Pi PicoでCircuitPythonを使ったプログラミング方法を開発環境Thonnyを使用してインストールからライブラリの追加、サンプルプログラム(コピペ)による動作確認(液晶表示SSD1306を例に)まで詳しく紹介します。
ラズパイPico W Wi-Fi遠隔操作CircuitPython

この「IPアドレス(上画像では192.168.0.13:80)」をクリックするか、ブラウザのアドレスバーに入力すると、以下のようなページが表示されます。

IPアドレスの後ろについている「:80」はポート番号で「:80」は省略可能なため、今回の場合は「192.168.0.13」だけでアクセスできます。
プログラム内でポート番号を変更した場合は入力する必要があります。
ラズパイPico W Wi-Fi遠隔操作MicroPython
「基本編」と機能的には同じですが「応用編」ではボタンを押すたびにページが更新されることはありません。
本体温度も1秒ごとに自動で更新されて表示されます。
この動作を実現するためには、Webページを「html」だけでなく「JavaScript」を使用して作成します。

・サンプルプログラム(コピペ)

「応用編」のサンプルプログラムは以下になります。コピペで貼り付けて実行してください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでもコピーできます。

12,13行目の「ssid」と「password」は、自宅のネットワーク環境に合わせて設定してください。
import socketpool       # TCP/IP接続用のソケット管理用
import wifi             # WiFi接続を管理用
import digitalio        # デジタルピンの入出力を制御用
from board import *     # マイクロコントローラーのボード定義から全てのピンをインポート
import microcontroller  # マイクロコントローラーに関する情報(CPU温度等)を取得
from adafruit_httpserver import Server, Request, Response # HTTPサーバー用ライブラリ

led = digitalio.DigitalInOut(LED)           # 本体LEDをGPIO端子に割り当て
led.direction = digitalio.Direction.OUTPUT  # LED端子を出力に設定

# Wi-FiアクセスポイントのSSIDとパスワードを設定(直接指定する場合)
ssid = "自宅のネットワーク環境のSSIDを設定"          # Wi-Fi接続先SSID
password = "自宅のネットワーク環境のパスワードを設定"  # Wi-Fi接続先パスワード
port_num = 80               # ポート番号(標準は80)

# Wi-Fi接続を実行
wifi.radio.connect(ssid, password)  # 指定したSSIDとパスワードで接続
print("Connected to", ssid)         # 接続先SSID表示

# ソケットプールを作成してHTTPサーバーを起動するための準備
pool = socketpool.SocketPool(wifi.radio)  # Wi-Fi接続上でソケットを管理するためのオブジェクトを作成
server = Server(pool, "/static", debug=True)      # サーバー起動時にPicoWのルートディレクトリからデータを提供できるようにする

# Webページを準備(HTMLコードを文字列として埋め込む)
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 str(html)

# ルート(IPアドレス:ポート)アクセス時に実行される関数(トップページ表示、ポート80は省略可)
@server.route("/")
def base(request: Request):  # HTTPリクエストを処理する関数を定義
    html = webpage()         # Webページのデータ取得して文字列で格納
    return Response(request, html, content_type="text/html")  # レスポンスとして「HTML」を返す

# 本体LED点灯(IPアドレス:ポート/lighton)アクセス時に実行される関数(ポート80なら省略可)
@server.route("/lighton")
def light_on(request: Request):
    led.value = True              # 本体LEDを点灯
    return Response(request, "LED_ON!", content_type="text/plane")  # レスポンスとして「テキスト」を返す

# 本体LED消灯(IPアドレス:ポート/lightoff)アクセス時に実行される関数(ポート80なら省略可)
@server.route("/lightoff")
def light_off(request: Request):
    led.value = False             # 本体LEDを消灯
    return Response(request, "LED_OFF!", content_type="text/plane")

# 本体温度取得(IPアドレス:ポート/get_data)アクセス時に実行される関数(ポート80なら省略可)
@server.route("/get_data")
def get_data(request: Request):
    temp = microcontroller.cpu.temperature  # 本体温度センサの温度取得
    temp = "{:.1f}".format(temp)            # 温度の値を小数点1桁に成形
    return Response(request, temp, content_type="text/plane")

# HTTPサーバーを開始して待ち受けるIPアドレスを表示する
print(f"Listening on http://{wifi.radio.ipv4_address}:{port_num}")  # f文字列を使って「IPアドレス」と「ポート番号」を埋め込んで表示
server.serve_forever(str(wifi.radio.ipv4_address), port=port_num)   # HTTPサーバーを指定された「IPアドレス」と「ポート」で開始

・プログラムの詳細

プログラムの詳細については「ライブラリ」のインポートと「Wi-Fi接続」までは「基本編」と同じです。「Webページの準備」からは以下のように異なります。

Webページは「基本編」と同様に文字列として変数に格納しますが、文字列の中に変数を埋め込む必要はないので「f “”” 〜 “””」ではなく「f」なしの「””” 〜 “””」を使用します。

Webページで表示するデータは「JavaScript」の「FetchAPI」で取得して「htmlの要素」を取得して書き換えることで表示を更新します。
詳細は「7.動作確認用のWebページの作成はChatGPTにおまかせ」で紹介しています。
# Webページを準備(HTMLコードを文字列として埋め込む)
def webpage():
    html = """
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8">
        <title>PicoW Controller</title>
        <style>
          """
          中略(CSS)
          """
        </style>
      </head>
      <body>
        """
        中略(HTML)
        """
        <script>
          """
          中略(JavaScript)
          """
        </script>
      </body>
    </html>
    """
    return str(html)

次に以下のように「HTTPサーバー」の設定を行います。
まず、ルート(IPアドレス:ポート)に対してリクエストがあった時に「htmlコード」をレスポンスとして返します。

# ルート(IPアドレス:ポート)アクセス時に実行される関数(トップページ表示、ポート80は省略可)
@server.route("/")
def base(request: Request):  # HTTPリクエストを処理する関数を定義
    html = webpage()         # Webページのデータ取得して文字列で格納
    return Response(request, html, content_type="text/html")  # レスポンスとして「HTML」を返す

その他のリクエストに対しては、リクエストアドレスごとに以下のように実行する処理を設定し、それぞれの動作に応じた「テキスト(文字列)」をレスポンスとして返します。

# 本体LED点灯(IPアドレス:ポート/lighton)アクセス時に実行される関数(ポート80なら省略可)
@server.route("/lighton")
def light_on(request: Request):  # HTTPリクエストを処理する関数を定義
    led.value = True             # 本体LEDを点灯
    return Response(request, "LED_ON!", content_type="text/plane")  # レスポンスとして「テキスト」を返す

# 本体LED消灯(IPアドレス:ポート/lightoff)アクセス時に実行される関数(ポート80なら省略可)
@server.route("/lightoff")
def light_off(request: Request):
    led.value = False            # 本体LEDを消灯
    return Response(request, "LED_OFF!", content_type="text/plane")

# 本体温度取得(IPアドレス:ポート/get_data)アクセス時に実行される関数(ポート80なら省略可)
@server.route("/get_data")
def get_data(request: Request):
    temp = microcontroller.cpu.temperature  # 本体温度センサの温度取得
    temp = "{:.1f}".format(temp)            # 温度の値を小数点1桁に成形
    return Response(request, temp, content_type="text/plane")

最後は「基本編」と同様に以下のように「HTTPサーバー」を開始します。

# HTTPサーバーを開始して待ち受けるIPアドレスを表示する
print(f"Listening on http://{wifi.radio.ipv4_address}:{port_num}")  # f文字列を使って「IPアドレス」と「ポート番号」を埋め込んで表示
server.serve_forever(str(wifi.radio.ipv4_address), port=port_num)   # HTTPサーバーを「IPアドレス」と設定した「ポート」で開始

以上で「PicoW」が「HTTPサーバー」として機能し、クライアントからのリクエストを常に待ち受ける状態になります。
リクエストがあった場合は設定した処理を実行し、設定したデータをレスポンスとして返します。

クライアント側が受信したレスポンスは「JavaScript」で処理され、Webページを書き換えることで表示を更新します。

6.本体に別ファイルで保存したWebページを読み込む方法

サンプルプログラムの「応用編」のようにWebページの「HTMLコード」が長くなる場合は、別ファイルとして保存しておいた方がプログラムが見やすく、編集もしやすくなるので、この方法も紹介します。

・Thonnyを使用したファイルの保存、実行方法

開発環境「Thonny」を使用した方法を以下のように順を追って紹介していきます。

以下画像のコピペで貼り付けているプログラムはサンプルプログラムとは異なりますが、作業手順は同じです。
ラズパイPico W Wi-Fi遠隔操作CircuitPython

まずは「CircuitPython」のファームウェアを書き込んだ「PicoW」をパソコンと接続して「Thonny」を起動すると上画像のように[untitled]タブが表示されます。

ラズパイPico W Wi-Fi遠隔操作CircuitPython

[untitled]タブのところに、この下で紹介している「Pythonサンプルプログラム」をコピペで貼り付けて、上画像のように[保存]ボタンをクリックします。

ラズパイPico W Wi-Fi遠隔操作CircuitPython

上画像のように保存先を確認するウインドウが表示されるので[Raspberry Pi Pico]の方をクリックします。

ラズパイPico W Wi-Fi遠隔操作CircuitPython

上画像のように「PicoW」内のルートディレクトリの内容が表示されるので、この中から[code.py]を選択して[OK]ボタンをクリックして上書き保存します。

ラズパイPico W Wi-Fi遠隔操作CircuitPython

次に上画像のように「新規ファイル」ボタンをクリックします。

ラズパイPico W Wi-Fi遠隔操作CircuitPython

新規で[untitled]タブでファイルが作成されるのでここに、この下で紹介している「HTMLサンプルプログラム」をコピペで貼り付けます。

ラズパイPico W Wi-Fi遠隔操作CircuitPython

上画像のようにHTMLコードを貼り付けたら[保存]ボタンをクリックします。

ラズパイPico W Wi-Fi遠隔操作CircuitPython

保存先は[Raspberry Pi Pico]の方を選択します。

ラズパイPico W Wi-Fi遠隔操作CircuitPython

上画像のように「PicoW」内のルートディレクトリの内容が表示されたら、「ファイル名:」の欄に「index.html」と入力して[OK]ボタンをクリックして保存します。


次に、今回はWi-Fi接続のための「SSID」と「パスワード」も外部設定ファイル「setting.toml」から読み込んでいますので、この方法も紹介します。

ラズパイPico W Wi-Fi遠隔操作CircuitPython

上画像のように[ファイルを開く]ボタンをクリックします。

ラズパイPico W Wi-Fi遠隔操作CircuitPython

[Raspberry Pi Pico]を選択して「PicoW」内のルートディレクトリの内容を表示させます。

ラズパイPico W Wi-Fi遠隔操作CircuitPython

上画像のように表示されたら、環境変数設定ファイル「setting.toml」を選択して[OK]ボタンを押します。

ラズパイPico W Wi-Fi遠隔操作CircuitPython

上画像のように「setting.toml」ファイルが開かれますが、初期状態は何も記入されていません。
ここにプログラムに読み込みたい値を変数として設定していきます。
今回はWi-Fi接続のための「SSID」と「パスワード」を以下のように設定しているため、以下のコードをコピペして貼り付けてください。

# setting.tomlにWi-Fi接続「SSID」と「パスワード」を設定
CIRCUITPY_WIFI_SSID="自宅のネットワーク環境のSSIDを設定"
CIRCUITPY_WIFI_PASSWORD="自宅のネットワーク環境のパスワードを設定"
ラズパイPico W Wi-Fi遠隔操作CircuitPython

上画像のようにコピペして貼り付けたら、黄色の下線部2箇所をご自宅のWi-Fi通信環境に合わせて書き換えてから「保存」ボタンをクリックします。

ラズパイPico W Wi-Fi遠隔操作CircuitPython

これで全ての設定が完了しました。
上画像のように[code.py]タブをクリックして、[現在のスクリプトを実行]ボタンをクリックします。

ラズパイPico W Wi-Fi遠隔操作CircuitPython

上画像のようなエラーが表示される場合はライブラリが不足しています。
以下のリンクを参考に「Adafruit社」の「adafruit_httpserver」ライブラリを「PicoW」本体の「libフォルダ」保存してください。

ライブラリの保存方法は以下のリンクで詳しく紹介しています。

ラズパイPicoの使い方 CircuitPython&開発環境Thonny
Raspberry Pi PicoでCircuitPythonを使ったプログラミング方法を開発環境Thonnyを使用してインストールからライブラリの追加、サンプルプログラム(コピペ)による動作確認(液晶表示SSD1306を例に)まで詳しく紹介します。
ラズパイPico W Wi-Fi遠隔操作CircuitPython

エラーが発生せずに実行されると上画像のように[シェル]部にIPアドレスでリンクが表示されるのでこれをクリックします。

ラズパイPico W Wi-Fi遠隔操作MicroPython

ブラウザが起動し上画像のような画面が表示されます。
「ON/OFF」ボタンを押すと「PicoW」の本体LEDが「点灯/消灯」し、その下にLEDのON/OFF状態が表示されます。
一番下には「PicoW」本体の温度が1秒ごとに更新されて表示されます。

以上がWebページのデータを外部ファイルから読み出す方法です。

今回は環境変数ファイルの「setting.toml」を使用しました。
「setting.toml」ファイルのデータは「os」ライブラリをインポートして使用します。
値を取得するには「os.getenv()」関数で変数名を指定して取得します。
詳細は以下の「Pytnonサンプルプログラム」を参照してください。

・Pythonサンプルプログラム(コピペ)

Webページを別ファイルから読み出すための「Python」プログラムは以下のようになります。

サンプルプログラムは以下になります。コピペで貼り付けて実行してください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでもコピーできます。

16,17行目の「ssid」と「password」は、自宅のネットワーク環境に合わせて設定してください。
import os               # ファイル操作など、OSレベルの機能を提供するために使用
import socketpool       # TCP/IP接続用のソケット管理用
import wifi             # WiFi接続を管理用
import digitalio        # デジタルピンの入出力を制御用
from board import *     # マイクロコントローラーのボード定義から全てのピンをインポート
import microcontroller  # マイクロコントローラーに関する情報(CPU温度等)を取得
from adafruit_httpserver import Server, Request, Response, FileResponse # HTTPサーバー用ライブラリ

led = digitalio.DigitalInOut(LED)           # 本体LEDをGPIO端子に割り当て
led.direction = digitalio.Direction.OUTPUT  # LED端子を出力に設定

# グローバル変数
state = "OFF!"  # 本体LED状態格納用

# Wi-FiアクセスポイントのSSIDとパスワードを取得(setting.tomlから取得する場合)
ssid = os.getenv("CIRCUITPY_WIFI_SSID")         # Wi-Fi接続先SSID
password = os.getenv("CIRCUITPY_WIFI_PASSWORD") # Wi-Fi接続先パスワード
port_num = 80                                   # ポート番号(標準は80)

# Wi-Fi接続を実行
wifi.radio.connect(ssid, password)  # 指定したSSIDとパスワードで接続
print("Connected to", ssid)         # 接続先SSID表示

# ソケットプールを作成してHTTPサーバーを起動するための準備
pool = socketpool.SocketPool(wifi.radio)      # Wi-Fi接続上でソケットを管理するためのオブジェクトを作成
server = Server(pool, "/static", debug=True)  # サーバー起動時にPicoWのルートディレクトリからデータを提供できるようにする

# ルート(IPアドレス:ポート)アクセス時に実行される関数(トップページ表示、ポート80は省略可)
@server.route("/")
def base(request: Request):       # HTTPリクエストを処理する関数を定義
    return FileResponse(request, filename='index.html', root_path='/')  # レスポンスとして「index.html」ファイルを返す

# 本体LED点灯(IPアドレス:ポート/lighton)アクセス時に実行される関数(ポート80なら省略可)
@server.route("/lighton")
def light_on(request: Request):
    led.value = True              # 本体LEDを点灯
    return Response(request, "LED_ON!", content_type="text/plane")  # レスポンスとして「テキスト(文字列)」を返す

# 本体LED消灯(IPアドレス:ポート/lightoff)アクセス時に実行される関数(ポート80なら省略可)
@server.route("/lightoff")
def light_on(request: Request):
    led.value = False             # 本体LEDを消灯
    return Response(request, "LED_OFF!", content_type="text/plane")

# 本体温度取得(IPアドレス:ポート/get_data)アクセス時に実行される関数(ポート80なら省略可)
@server.route("/get_data")
def get_data(request: Request):
    temp = microcontroller.cpu.temperature  # 本体温度センサの温度取得
    temp = "{:.1f}".format(temp)            # 温度の値を小数点1桁に成形
    return Response(request, temp, content_type="text/plane")

# HTTPサーバーを開始して待ち受けるIPアドレスを表示する
print(f"Listening on http://{wifi.radio.ipv4_address}:{port_num}")  # f文字列を使って「IPアドレス」と「ポート番号」を埋め込んで表示
server.serve_forever(str(wifi.radio.ipv4_address), port=port_num)   # HTTPサーバーを指定された「IPアドレス」と「ポート」で開始

・HTMLサンプルプログラム(コピペ)

「Python」から読み出すWebページのプログラムは以下のようになります。
ファイル名は「index.html」として「PicoW」本体のルートディレクトリに保存してください。

サンプルプログラムは以下になります。コピペで貼り付けて実行してください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでもコピーできます。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <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>

7.動作確認用のWebページの作成はChatGPTにおまかせ

以下は「MicroPython」の時にもやった「JavaScript」を使用したWebページを「ChatGPT」に書いてもらったものです。
質問の仕方によっては1回で目的の動作をするコードを書いてくれます。

今回使用した「応用編」のサンプルプログラムのWebページは以下の質問でほぼ同じものを書いてくれるので試してみましょう。

「ChatGPT」への質問は以下のように行いました。

以下の仕様を満たすHTMLタグをCSS、JavaScriptを含めて書いて
1. ブラウザにONボタン(内容:ON、色:緑色)とOFFボタン(内容:OFF、色:赤色)を横並びに配置して、その下に隙間を開けて、LEDの状態を表示する段落を配置(初期値は空白)して、その下に隙間を開けて、項目を温度表示としたテキストボックスを配置して、全体を上中央に表示する。
2. ONボタンを押すと非同期通信のfetchで”/lighton”へリクエストを送信し、受信したレスポンスをテキストで取得して、LEDの状態を表示する段落の内容を取得したテキストの内容で更新、かつコンソールログに表示する。
3. OFFボタンを押すと非同期通信のfetchで”/lightoff”へリクエストを送信し、受信したレスポンスをテキストで取得して、LEDの状態を表示する段落の内容を取得したテキストの内容で更新、かつコンソールログに表示する。
4. 1秒ごとに”/get/data”へリクエストを送信し、レスポンスを温度表示用のテキストボックスに表示する。

「ChatGPT」の回答は以下のようになりました。

以下は、上記の仕様に従ってHTML、CSS、JavaScriptを書いた例です。
<!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>
以上が、仕様に沿ったHTML、CSS、JavaScriptの例です。このコードを実行すると、上部に「ON」と「OFF」のボタンがあり、下部にLEDステータス表示パラグラフ、および温度表示用のテキストボックスがあります。ユーザーは、ボタンをクリックして、LEDの状態を制御でき、1秒ごとに温度データが自動的に更新されます。

以上のような回答になりました。


このコードを「応用編」のサンプルプログラムの「webpage()関数」の中の「html = ”'” 〜 “””」の中で指定した文字列と置き換えるか、別ファイルとして読み込む場合はそのまま「index.html」として保存して実行すると、以下のようなWebページが表示されます。

ラズパイPico W Wi-Fi遠隔操作MicroPython

残念ながら「LED_ON!」の表示だけ左端に表示されたため、上コードの中の「 7〜9行目」の「CSS」だけ後から追加しています。

修正したものを実行すると下画像のようになり、思い通りのWebページになり問題なく動作しました。

ラズパイPico W Wi-Fi遠隔操作MicroPython
「ChatGPT」への質問を正しくしてやれば1回で使えるコードを書いてくれます。
動かなかったとしても、症状や発生したエラーを続けて聞けば正しいものに書き換えてくれます。
今回使用した「ChatGPT」のAIは「ChatGPT-3.5 Turbo」です。
無料の「Text-davinci-003」では、「html」「JavaScript」「CSS」を個別で回答してきたので、それぞれを自分で組み合わせる必要がありました。
回答のコードは聞くごとに微妙に異なります。何度か聞いて一番良いものを使用しましょう。

ChatGPTを使用したプログラミングについては、以下のリンクで紹介しています。

Arduinoプログラミング初心者必見!ChatGPTがもたらす新しい可能性
ChatGPTがプログラミングの考え方を変えました「こんなプログラムを書いて」と聞けばコード作成から説明、エラーの指摘までしてくれる。ChatGPTがもたらす新しい可能性を探ってみたいと思います。

8.まとめ

「CircuitPython」でWi-Fi通信のサーバー機能を使用した、ブラウザからの遠隔操作、データ監視の方法を詳しく紹介しました。

「MicroPython」よりもWi-Fi接続からサーバー設定までの流れがわかりやすく、レスポンスに対しての動作もデコレーターで関数ごとにまとめられるため、見やすくてプログラムの作成も編集もやりやすいのではと思います。

今回「MicroPython」に続けて「CircuitPython」で実装しましたが、ここでやった方法で比較すると「CircuitPython」の方が使いやすいと感じました。

「CircuitPython」の特徴である、内部データをUSBメモリのようにフォルダで開いて操作できるため、Webページ用の「HTMLファイル」を単独で作成し保存、編集ができるためプログラミング効率が良いと思います。
「JavaScript」や「CSS」がより複雑になる場合は、これらも別ファイルとして分離させるとさらに効率は上がると思います。

動作確認用のブラウザページ(html、JavaScript、CSS)は「ChatGPT」にお任せしてしまえば、ある程度のものは書いてくれるため、動作確認しながら、質問の仕方を身につけて、Webページ作成のスキルも身につけていきましょう。

ラズパイPicoW Wi-Fi通信、遠隔操作&表示 MicroPython編
Raspberry Pi PicoWのWi-Fi無線通信を使用してMicroPythonでパソコンやスマホのブラウザから遠隔操作,データ表示する方法を詳しく紹介
ラズパイPicoW Wi-Fi通信、遠隔操作&表示 C言語(Arduinoコマンド)編
Wi-Fi通信機能を使用してPicoWをサーバーに設定して遠隔操作&表示する方法を詳しく紹介します。動作確認のブラウザページはChatGPTに全部書いてもらいましょう。

コメント

  1. とんかつ より:

    お邪魔いたします

    (6)まで、試しました。

    circuitpythonでの実行は初めてです。
    他のページでMicroPythonでの例題はあったので、
    試しては、いましたが・・・その後
    私の環境ではMicroPythonではライブラリのinstallが
    出来ない問題があり、後は全てcircuitpythonでの
    試行になりました。

    それにしても、Wifiに接続出来ると、こんな事も
    出来るのですね。双方向・・
    インタラクティブ(わっ かっこいい)な事も
    出来る、面白いですね。

    先の、htmlに付いては、まだ課題が、大きすぎて
    時間がかかりそうです、
    一冊、参考書は手元に有るのですが、
    開いた時は、なかなか難しく読み進められませんでした :)
    積ん読状態になってます :)

    ありがとうございます

    • logikara より:

      またまたこんばんわ^^
      (6)まで進めば、ほぼ制覇してますね!ペースが早い!

      htmlについては(7)でも紹介してますが「ChatGPT」に丸投げでいいかと思います。
      ある程度の基礎や仕組みの理解はしておいた方がいいですが、聞き方さえわかればほぼ書いてくれるのであとはコピペです。

      私も参考書は苦手で、買って満足した参考書がいっぱいw
      難しいことは「ChatGPT」のお任せしましょう♪

  2. とんかつ より:

    お答え ありがとうございます。

    「ChatGPT」は、私には まだ、まだ先のことであります。

    でも
    「GoogleのBard」 「Bard」 鳥かと思ったら鳥とは
    綴りか違いました :)

    『吟遊詩人』ですって!すごい、ネームングです。
    少しセンスを感じます。

    情報のリテラシー、選択眼がますます、大事に成ってきましたね

    しかし、良く出来ています。
    聴き方がありますが、割と、まともに返してくれます。
    検索ボックスのキーワードの
    入れ方が変わって来ましたよ。

    すごい、世の中になりましたね。
    AIがプログラミングしてくれるなんて!
    もう、願ったり!叶ったり! ですが・・・

    [ Selenium ]で何とか
    JCBA傘下の局に、簡単にアクセス出来ないかと
    (全国コミュニュティFM放送局連盟?)
    探ぐって観ましたが、
    ラズパイのChromiumは遅すぎて
    フォーカスが合わないみたいか?上手く活きませんでした。
    局の再生ボタンが「深くて」ストレートに到達
    出来ないいんです。
    北海道~九州の局まで数局聴くのですが
    ちょっとアクセスが面倒なんです。
    昔は、ミュージックバードから
    ラズパイで簡単に直に聴けたのですが
    ラジコと同じで、『途中でお金を抜く組織』?が
    介在するようになって、面倒です。 :)

    PC上では、出来るかも知れませんが。
    ラズパイで挫折のままです。 :)

    今はPicoの時間が多くなっています。

    ありがとう ございまっす。

    • logikara より:

      こんばんわ^^
      htmlなら「Bard」でもそれなりに書いてくれると思いますよ。

      「selenium」は私は使ったことありませんが、人が使ってるのを見た時はhtmlの要素(div、p、input、btton等)や
      id、classがわかれば目的の場所を指定できるイメージでした。

      chromeなら[その他ツール]→[デベロッパーツール]を表示して「要素」から目的の要素(button?)をコピペで「Bard」に貼り付けて
      「この要素をseleniumでクリックする方法は?」のように聞けば書いてくれるのではないでしょうか?(想像ですが・・・)
      picoでできるかどうかは謎ですw

      しかし、いろんな使い方がありますね〜私はC言語が好きなんですけど、今はpythonですかね
      私も時間ができたらやってみますね♪

  3. とんかつ より:

    お邪魔します :)

    基本的過ぎて、恐縮ですが・・・・
     I have a Question

    私には、ちょっと、ややこしい。

    scriptの枕ことばで

    from import
    こんな記述が良くアリマスネ。

    これは
    モジュールの中にある関数、変数を
    ダイレクトに使えるようにしている。

    注意点として、from importで関数を直接読み込んだ場合
    モジュール内の他の関数は使えなくなります。
    関数・変数を「指定」して使って居る為という。

    ところが・・
    この<関数・変数名>の箇所に
    例えば
    from random import *
    の様に [ * ]を使って居る場合も有ります・・・
    この場合は、
    randomモジュール内の<関数・変数>は
    指定しない、
    つまり適宜このモジュールの
    <関数・変数は>使えるものは、何でも使いますよ』ということ
    でしょうか。?

    丁度、ババ抜きのオールマイティ・カードの様に。

    お教え下さい。

    この辺の理解が遅れていて
    エラーを、毎度、頂戴いたしております。:)

    ありがとうございます

    • logikara より:

      こんばんわ^^

      pythonはあまり使い込んでませんので私の理解の範囲になりますが
      CircuitPythonのboardモジュールを例にして動作確認してみるとわかりやすいのではないでしょうか?

      基本的に以下2つは同じ動作が実現できます。
      import board
      from board import *
      「*」はおっしゃる通りババ抜きのオールマイティカードのようなもので「ワイルドカード」と呼ばれ、「全て」という意味で使われます。

      このため「from board import *」は「boardモジュール」の全てをインポートするということで「import board」と同じ意味を持ちます。

      明確な違いは以下です。
      「import board」の場合 : その後のコード作成の中で使用するもの全てに「board.〜」と書く必要がある。
      「from board import *」の場合 : 「board.」を省略できる。

      注意が必要なのは「*」を使用しない場合です。
      例えば「from board import GP0」とした場合、GP0の端子は「board.」を省略して使用できますが、その他の端子は指定できなくなります。
      もしGP1と書いた場合、内部的には「board.board.GP1」となるため「そんなものはない」と怒られます^^;。
      GP1も使いたい場合は「from board import GP0 GP1」と書けば使えます。

      「from 〜 import 〜」を使う場合はコードが省略できるので、可読性や作業性をあげたい時に使うと良いと思います。
      また、コード内で使用する端子を先に明確にしておきたい場合や、限定してその端子しか使わせないようにしたい場合にも使用できると思います。

      しかし、「from 〜 import 〜」と書いたことで、他の変数名や関数名と被ったりすると、これまたエラーになるため、
      めんどくさくても「import」として、全部書くのが確実かもしれません。
      この辺りはコードの規模を踏まえて、作業性や可読性を考慮しての判断となるので、状況によるかと思います。

      ・・・という私の理解ですが、ご参考までに^^

      • logikara より:

        早速ですが訂正(汗)
        私も動作確認してみたら以下の部分は間違いですね^^;

        誤:「もしGP1と書いた場合、内部的には「board.board.GP1」となるため〜」
        正:「もしGP1とかいても、boardモジュールの中のGP0しかインポートされていないため〜」

        「from board import GP0 GP1」もカンマが抜けてますね(汗)
        「from board import GP0, GP1」が正しいです。

        失礼しました。

  4. とんかつ より:

    * の件

    ありがとうございます。

    私の質問の書き方も変でした・・・
    from import
    こう書いたつもりが、間違っていました、
    申訳ありません、
    でも、お聴きしたポイントは
    ちゃんとお解り頂いたので、疑問が解けました:)

    で、*は、オールマイティ・カードと同じ意味になる
    これで、大分わかりました、
    このアスタリスク付きの記述が時々有るので
    たぶん、ソウかなとおもっていました、

    こんな時間まで何を?ですが。。。。
    4BでSSD起動起動が出来る記事を見たので
    それを、試していたのですが、
    私のところでは、usbメモリからも
    ssdからも起動出来ませんでした、
    ラズパイは、全てヘッドレス運用で
    SSH接続です、どうも、ssh認証まで開きましたが
    pwが通らず、結局、元のsdカードに戻しました。
    ssdやusbメモリに直接Pi-OSを書き込んでいたので
    時間がかかりました、初回起動時も時間がかかるので
    かなり、同じ様な作業を繰り返していましたが、
    成功はしませんでした。

    また、Seleniumでは、chromeドライバを変に弄ってい
    仕舞ったらしくブラウザのchromeがおかしくなり
    吊られて、Microsoft Edge まで挙動がおかしくなり
    かなり焦りましたが、PCの再起動で戻りました。

    「Bard」も答えてくれましたが、こちらも上手く活きませんでした。
    前に、1ヶ月間くらい、あれこれ試したんですが
    大分、前で内容を忘れています。
    ページの作りが複雑になっているらしく、
    ポイントに当てられない!
    Seleniumを使うと、
    scriptでマウスやkeyboardからのアクションが代用出来る
    と言うので、それなら
    私がWebラジオを聴く時と同じで
    シェルスクリプト・ファイルのファイル名入力で
    選局出来る(局によっては、1文字+エンターで選局・再生する)
    事と同じ様に成るはずと思い、やって観たかったのです。

    この時、HTMLの参考書を買ったのですが
    『痒いところに届かない』内容で、アマゾンからでしたが
    書店で「品定め」後、購入すべきでした。
    田舎で大きな書店は無いのです・・
    情報はネットからのみです。

    Sejeniumからの利用は
    ダメでやりようがなかったので、これまでと思いすっかり忘れました。

    そうで無くとも、忘れるのは一番得意です。:)

    それや、これやで 今夜は 遅くなりました。

    ありごとうございました。

  5. とんかつ より:

    あれ!
    <>を使うと、消えますね!!

    • logikara より:

      おはようございます^^
      <>を使うと消えるのは、htmlタグとして認識されるからと思います。

      夜遅くまで熱心ですね〜ラズパイ4BがSSDで起動できたらいいですね。
      それで動作が早くなるなら、もう一度ラズパイ4Bやてみようかなとも思いますが・・・
      1日中ほぼ設定作業で、何やってたんだろ〜な日々がつらすぎて、すっかり遠ざかってしまいました^^;

      Webラジオの件は私に全く知識がなく、お力になれそうにないです(汗)
      そういう世界もあるんだなと電子工作の奥深さを知りました。

      先日の「XIAO RP2040」のOLED表示についてですが、届いたので動作確認してみました。
      こちらではピン番号さえ合わせれば動くようです。
      せっかく買ったので次の投稿は「XIAO RP2040」の紹介を予定してます。
      OLEDを接続しておかないとエラーは出るようなので、その辺も踏まえて記事作成中です。
      また見てください♪

  6. とんかつ より:

    お邪魔いたします

    それは、楽しみです!!

    ===
    私は「メモ帳」に[ Mery ]というアプリを使って居ます。
    こちらのページにお邪魔するときも
    この[ Mery ]に記述し読み返してから、
    「コピーし」こちらのページに「貼り付け」ます。

    それで、この前のお尋ねで、
    from import
    と記述した箇所が、このコメント・ページでは
    『from import 』
    になってました、という意味で、

    <>を使うと消えると 申しあげました。

    from モジュール名 import 関数・変数名

    とすれば、
    モジュール名 関数・変数名は 消えなかったかも知れませんが
    このコメント・ページに<>を含んで
    記述しそそれを貼り付けると< >の中の文字が消えていた」という
    事でした。

    判り難くて 面倒で、済みません。

  7. とんかつ より:

    あ、やっぱり 消えますね。

  8. とんかつ より:

    Mery の 問題かも知れません
    面白いですね!

    • logikara より:

      このページもhtmlでできてるので半角< >はタグとして認識されるため<br>と書けば
      改行されたり
      <strong>で囲むと強調されたりします。</strong>
      ブラウザで確認しないと見た目は分かりませんけどね^^;

      • とんかつ より:

        な~るほど!
        そうなんですね!
        では、私は迂闊に<や>は使わない方が、まごつかなく済みそうです:)

        失礼いたしました。
        ありがとうございます。

        PCでSeleniumを使って、Googleの検索ページまでは
        開ける様になりました。
        一歩前進でです、:)
        もっとも、ここまでの手法は、ネットで良く
        紹介されています。

        実行ファイル.pyを書き、任意の場所に保存し
        PowerShellを開いてプロンプトに続いて
        python 実行fileのアドレス\実行file.py
        でOK。
        これは、私がWebラジオを聴くときと全く同じ手法でした。
        どうやるのかと思っていたら、解ってしまうと
        『な~~んだ!そうだったのか!』でした:)
        同じ、Python ですもの!
        しかし、Windowsの中で特別の手続きもせずにPythonが動くのが、
        なんだが不思議です :)
        PythonにもPATHを通してあるからなのでしょうか?

        たしか、以前も「Googleの検索ページを開く」まででは
        出来ましたが、
        JCBAの方は、
        まだ、まだ、肝心の的をクリックさせられない。

        Bard が自信満々にくれるscriptは、私の聴き方が悪いのか
        試すと、全くダメです。
        まあ、1行1行ネットで教えてもらいながらです。

        ラズパイではヘッドレス運用なので tera turm を使い
        コンソールも当然 tera turm の黒い画面に入力します、
        動作が遅いと言う事は、全く無いのですが

        PCではPowerShellに打ち込んでいますが、これがまた、
        プロンプロが出るまでに時間がかかります。
        ショートカットでPowerShellは直ぐに「開きはします」が
        入力出来る状態には15~20秒位待たされます。
        何をやっても時間がかかるので、20秒やそこら
        どうってこと無いのですが・・・ でも「遅い」!

        検索したら、PowerShellを速く開く方法があると言うので
        早速、そのファイルをPowerShellで実行して見ましたが
        変化は無かったです。:)

        PCは、Windows11 Home 23H2
        (インサイダーβ版) PowerSellは最新Ver.です

        なお、実行ファイル.pyを置いたディレクトリにPATHを
        通しましたが。。そのつもりなのですが・・・
        通らない!
        環境変数のPATHの項目に、私が追加したディレクトリ・アドレスは
        表示されています。:)

        パスが通っていなくとも
        実行時に 絶対パスで記述すれば、実行されるので
        タイピングの練習(コピペになりますが)と思って
        打ち込めば良いのですが・・

        失礼しました

        • logikara より:

          seleniumに関しては知識が無いのでお力になれそうにありません^^;
          やってみようかと思いましたがchromeDriverのインストール等、環境づくりがいるようなので
          また時間ができた時に・・・

          PowerShellの動きが遅いのは何でしょうね?それは経験したことがありません。
          pythonの実行は実行フォルダに「cd /path」で移動してから行うので
          特にパスを通して実行したこともないです。

          エディタはVSCodeを使用しているので、基本的に開発環境はエディタにお任せです。
          VSCodeは何でもできる万能エディタなのでPowerShellもいりません。
          「Mery」と併用でも良いので導入を検討してみてはいかがでしょう?

タイトルとURLをコピーしました