前回は「MicroPython」を使用したブラウザからの遠隔操作、データ監視の方法を紹介しましたが、今回は「CircuitPython」を使用した方法を詳しく紹介します。
動作確認用のブラウザページ(html、JavaScript、CSS)は今回も「ChatGPT」にお任せして、「CircuitPython」を使用したWi-Fi無線通信について理解し、自由に使いこなせるようになりましょう。
遠隔操作の方法として「サーバー」機能を利用していますが、ここで紹介する「サーバー」機能を使いこなせば「ChatGPT」のようなチャットボットを作ることもできるようになります。
これについても、作り方を以下のリンクで詳しく紹介しています。

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


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

1.ラズパイPico Wとは
2.開発環境について
・CircuitPythonとは
・Thonnyとは
・adafruit_httpserverライブラリの新旧の違いについて
3.MicroPythonとの違い
・USBメモリのように認識される
・環境変数設定ファイル「setting.toml」について(Ver.8以降)
4.「基本編」Wi-Fi接続、遠隔操作&表示
・動作紹介
・サンプルプログラム(コピペ)
・プログラムの詳細
5.「応用編」Wi-Fi接続、遠隔操作&表示(データ自動更新)
・動作紹介
・サンプルプログラム(コピペ)
・プログラムの詳細
6.本体に別ファイルで保存したWebページを読み込む方法
・Thonnyを使用したファイルの保存、実行方法
・Pythonサンプルプログラム(コピペ)
・HTMLサンプルプログラム(コピペ)
7.動作確認用のWebページの作成はChatGPTにおまかせ
8.まとめ
1.ラズパイPico Wとは
「Raspberry Pi PicoW」とは、イギリスを拠点とする慈善団体によって若者のプログラミング学習を目的に開発されたもので、他の「Raspberry Piシリーズ」とは異なり、OSの機能はありませんが、電源投入ですぐに使用でき、スイッチやLEDランプ、モーター、各種センサー、通信機器を接続して、それらを制御するプログラムを簡単に作成して動作確認できるため、組み込み(制御)系プログラミングの学習に最適です。
「Pico W」にはWi-Fi機能があり、Wi-Fi通信を利用したインターネット経由のデータ収集や遠隔操作等のアイデアを簡単に試すことができます。
端子配列は下画像のようになります(公式サイトより抜粋)

2.開発環境について
・CircuitPythonとは
「CircuitPython」とはマイコンボード(マイクロコントローラ)上での動作に最適化された「Python3」と互換性のある「MicroPython」から派生した、教育および初心者向けのオープンソースのプログラミング言語です。
「CircuitPython」の公式サイトは以下のリンクになります。

・Thonnyとは
「Thonny」とは「ラズベリーパイ」の上位機種にも標準でインストールされている「Python」のプログラム作成、デバッグ、実行を行うことがでる統合開発環境(IDE)です。
簡単なデバッグ機能やエラー情報の確認ができるため、学習におけるストレスを最小限に抑えることができ「Python」プログラマーにとって非常に役立つ開発環境と言えます。
エディタとしての機能は物足りないですが、手軽に導入できて特に初心者の方がお試しするには最適のツールと思います。
「CircuitPython」を使用した開発環境「Thonny」の使用方法は以下のリンクで詳しく紹介しています。

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

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

「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」の基本的な文法をサポートしていますが、ライブラリに関しては共通のものは一部で、多くのライブラリは専用のものを使用する必要があります。
主な特徴はそれぞれ以下のようになります。
- 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」というフォルダが認識されます。これを開くと以下のようなファイルが確認できます。

ファイルの内容は以下のようになります。
- 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社」のサイトを参照してください。
「setting.toml」は「PicoW」のルートフォルダに配置されており、このファイルに変数としてWi-Fi接続情報(SSID、パスワード)等を設定しておくことで、メインのプログラムから読み込んで使用することができます。
4.「基本編」Wi-Fi接続、遠隔操作&表示
「基本編」はシンプルなWi-Fi通信による、サーバーを使用した遠隔操作とデータ表示の方法を確認します。
開発環境「Thonny」の準備がまだの方は、以下リンクを参照してインストールしてください。

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


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

ブラウザ上にはボタンが2つあり、ボタンを押すとそれぞれ以下のような動作になります。
- [Light on]ボタン:「IPアドレス/lighton?」でサーバーにリクエストを送信し、サーバーがこれを受信した場合は本体LEDを点灯させ、LEDの状態変数「state」を「’ON’」にします。
- [Light off]ボタン:「IPアドレス/lightoff?」でサーバーにリクエストを送信し、サーバーがこれを受信した場合は本体LEDを消灯させ、LEDの状態変数「state」を「’OFF’」にします。
「PicoW」へのアクセスが成功して、ブラウザ上の各ボタンを操作すると「シェル」部には下画像のように通信結果が表示されていきます。

通信結果に表示されている「IPアドレス(画像では192.168.0.8)」はアクセスしてきたクライアントの「IPアドレス」です。
「”GET 〜”」にはクライアントからのリクエストアドレスが表示されます。横の数値はリクエスト時のデータ量です。
「”200 OK”」は通信成功を表します。横の数値はレスポンス時のデータ量です。
・サンプルプログラム(コピペ)
「基本編」のサンプルプログラムは以下になります。コピペで貼り付けて実行してください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでもコピーできます。
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」になります。

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

次に「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サーバーを起動するための「ソケット」を開きます。
以上が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)」が表示されます。


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

・サンプルプログラム(コピペ)
「応用編」のサンプルプログラムは以下になります。コピペで貼り付けて実行してください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでもコピーできます。
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ページを準備(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」を使用した方法を以下のように順を追って紹介していきます。

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

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

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

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

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

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

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

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

上画像のように「PicoW」内のルートディレクトリの内容が表示されたら、「ファイル名:」の欄に「index.html」と入力して[OK]ボタンをクリックして保存します。
次に、今回はWi-Fi接続のための「SSID」と「パスワード」も外部設定ファイル「setting.toml」から読み込んでいますので、この方法も紹介します。

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

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

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

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

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

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

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


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

ブラウザが起動し上画像のような画面が表示されます。
「ON/OFF」ボタンを押すと「PicoW」の本体LEDが「点灯/消灯」し、その下にLEDのON/OFF状態が表示されます。
一番下には「PicoW」本体の温度が1秒ごとに更新されて表示されます。
以上がWebページのデータを外部ファイルから読み出す方法です。
・Pythonサンプルプログラム(コピペ)
Webページを別ファイルから読み出すための「Python」プログラムは以下のようになります。
サンプルプログラムは以下になります。コピペで貼り付けて実行してください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでもコピーできます。
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」への質問は以下のように行いました。
「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 = ”'” 〜 “””」の中で指定した文字列と置き換えるか、別ファイルとして読み込む場合はそのまま「index.html」として保存して実行すると、以下のようなWebページが表示されます。

残念ながら「LED_ON!」の表示だけ左端に表示されたため、上コードの中の「 7〜9行目」の「CSS」だけ後から追加しています。
修正したものを実行すると下画像のようになり、思い通りのWebページになり問題なく動作しました。

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

8.まとめ
「CircuitPython」でWi-Fi通信のサーバー機能を使用した、ブラウザからの遠隔操作、データ監視の方法を詳しく紹介しました。
「MicroPython」よりもWi-Fi接続からサーバー設定までの流れがわかりやすく、レスポンスに対しての動作もデコレーターで関数ごとにまとめられるため、見やすくてプログラムの作成も編集もやりやすいのではと思います。
今回「MicroPython」に続けて「CircuitPython」で実装しましたが、ここでやった方法で比較すると「CircuitPython」の方が使いやすいと感じました。
「CircuitPython」の特徴である、内部データをUSBメモリのようにフォルダで開いて操作できるため、Webページ用の「HTMLファイル」を単独で作成し保存、編集ができるためプログラミング効率が良いと思います。
「JavaScript」や「CSS」がより複雑になる場合は、これらも別ファイルとして分離させるとさらに効率は上がると思います。
動作確認用のブラウザページ(html、JavaScript、CSS)は「ChatGPT」にお任せしてしまえば、ある程度のものは書いてくれるため、動作確認しながら、質問の仕方を身につけて、Webページ作成のスキルも身につけていきましょう。


コメント