前回はロボットを使ったライントレースを行いました。
今回はRaspberry Pi Pico Wに搭載されているWi-Fiモジュールを使って、ソケット通信を行います。
これによりロボットを遠隔で操作できるようになります。

ソケット通信とは
ソケット通信は、デバイス同士がネットワークを通じてデータをやり取りするための仕組みです。
ソケット通信にも様々な種類がありますが、代表的なUDPとTCPについて説明します。
情報のやり取りをするという事で、手紙と電話に例えて説明します。
手紙のやり取り(UDP通信)
- 手紙は、送りたい相手の住所(IPアドレス)を書いて送り届けます。
- 相手が受け取ったかどうかは分からないし、手紙が途中でなくなってしまう可能性もあります。
- これは「UDP通信」に似ています。データを送るのは簡単で速いけれど、届いたか確認できないことがあります。
電話での会話(TCP通信)
- 電話は「もしもし」で相手とつながることを確認してから話し始めます。
- 会話中に何か聞き取れなければ「もう一度言って」と確認することができます。
- これは「TCP通信」に似ています。データが確実に届くように、やり取りの中で確認を繰り返します。
ソケット通信の流れ
ソケットとは、デバイスの中にある窓口のようなものです。ソケット通信の流れを簡単に説明します。
- 準備
- サーバー側が「窓口を開ける準備をする(ソケットを開く)」。
- クライアント側は「データを送りたい窓口を探す(サーバーのIPアドレスとポート番号を指定)」。
- 接続
- クライアントが「データを送っていいですか?」とサーバーに連絡する(TCPの場合)。
- サーバーが「どうぞ!」と返事をする。
- データのやり取り
- クライアントが「こんにちは!これはデータです」とサーバーに送る。
- サーバーが「データを受け取りました!」と返事をする。
- 終了
- やり取りが終わったら、「さようなら!」と言って通信を切る。
UDP通信の窓口の流れ
UDP通信はシンプルで素早くデータを送信しますが、「確認」や「保証」がないのが特徴です。
UDP通信の例え
- あなたは役所の窓口で「資料」を投げ込む仕組みを想像してください。
- 以下の流れで進みます:
- データの送信:
- あなた(送信者)は窓口に資料を「ポンッ」と投げ入れます。
- 何も言わずにその場を立ち去ります。
- 相手の受け取り:
- 窓口の人(受信者)が資料を受け取るかどうか、あなたは気にしません。
- 資料が無くなったり、途中で紛失しても確認しません。
- 確認がない:
- もし相手が受け取ったか確認したい場合でも、その仕組みがありません。
- このため、効率は良いですが信頼性は低いです。
特徴(窓口例からのポイント)
- 速いけど雑:確認や手続きが不要なのでスピード重視。
- リアルタイム性が重要な場面(スポーツ中継やゲーム)に適している。
- 例: 役所に大量のチラシを一方的に届けるイメージ。
TCP通信の窓口の流れ
TCP通信は丁寧で確実にデータを届ける仕組みです。送信者と受信者の間で「確認」を行いながら進みます。
TCP通信の例え
- あなたが役所の窓口で資料を手渡しする仕組みを想像してください。
- 以下の流れで進みます:
- 接続の開始:
- あなた(送信者)は窓口に行き、「こんにちは、資料を渡したいです」と挨拶します。
- 窓口の人(受信者)が「分かりました、どうぞ」と返事をします。
- データの送信:
- あなたは資料を一枚一枚確認しながら渡します。
- 窓口の人も受け取るたびに「受け取りました」と確認します。
- 確認のやり取り:
- もし資料が途中で紛失した場合、窓口の人が「すみません、もう一度渡してください」とリクエストします。
- 終了の確認:
- 資料をすべて渡し終わったら、「これで終わりです」と伝えます。
- 窓口の人が「はい、受け取り完了です」と返事をして、やり取りが終わります。
特徴(窓口例からのポイント)
- 丁寧で確実:相手が受け取ったことを確認しながら進む。
- 信頼性が重要な場面(オンラインショッピングやメール)に適している。
- 例: 役所で手続きに必要な書類を一枚ずつ丁寧に渡すイメージ。
Raspberry Pi PicoとWindows PCの接続
通信の概要が分かった所で、パソコンとRaspberry pi picoを同じネットワーク上に繋ぎます。
一番簡単な方法はWindows PC上でモバイルホットスポット(≒テザリング)を立てる方法です。
Windowsの検索機能でモバイル ホットスポットと検索してください。モバイルホットスポットのシステム設定が開きます。
編集をクリックします。

ネットワーク名とパスワードを任意の物に設定します。
他の人と被らないようにしてください。必ず実習後にはパスワードを変えるようにしてください。

ホットスポットをオンにします。

他、スマホ等のデバイスでWi-Fiスポットが立っていることを確認出来たら完了です。
次にRaspberry Pi Pico Wを同じネットワークに接続します。
以下のプログラムを書きこみます。
SSIDとパスワードは今回作成したWi-Fiスポットと同じものにしてください。プログラムの取り扱いには注意してください。
import network
import socket
from time import sleep
import machine
ssid = 'NAME OF YOUR WIFI NETWORK'
password = 'YOUR SECRET PASSWORD'
def connect():
#Connect to WLAN
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
while wlan.isconnected() == False:
print('Waiting for connection...')
sleep(1)
print(wlan.ifconfig())
try:
connect()
except KeyboardInterrupt:
machine.reset()実行すると以下のような文がTerminalに表示されます.。
Waiting for connection...
Waiting for connection...
Waiting for connection...
Waiting for connection...
Waiting for connection...
('192.168.1.143', '255.255.255.0', '192.168.1.254', '192.168.1.254')次にソケットを開きます。ポート番号は基本80に設定しておきます。
import network
import socket
from time import sleep
import machine
ssid = 'AAAAAAAAAA'
password = 'AAAAAAAAAA'
PORT = 80
def connect():
#Connect to WLAN
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
while wlan.isconnected() == False:
print('Waiting for connection...')
sleep(1)
ip = wlan.ifconfig()[0]
# 追加
print(f'Connected on {ip}')
return ip
# 追加
def open_socket(ip):
# Open a socket
address = (ip, PORT)
connection = socket.socket()
connection.bind(address)
connection.listen(1)
print(connection)
return connection
try:
# 変更
ip = connect()
open_socket(ip)
# connection = open_socket(ip)
except KeyboardInterrupt:
machine.reset()実行すると以下のようなコードが出てきます。ここでsocket stateが1であれば問題なく接続できています。
>>> %Run -c $EDITOR_CONTENT
MPY: soft reboot
Connected on 192.168.137.215
<socket state=1 timeout=-1 incoming=0 off=0>これでWi-Fi通信の準備完了です。
Wi-Fiで送受信
Wi-Fiを使ってRaspberry Pi Picoでデータのやり取りをする方法を紹介します。
データを送信する方法にはいろいろあります。
たとえば、パソコンでPythonというプログラムを使って送ったり、Raspberry Pi Pico同士で直接データを送り合ったりすることもできます。
今回は、スマートフォンを使ってRaspberry Pi Picoとデータのやり取りができる方法を試してみます。
まずは、スマートフォンから入力できる仕組みを作るために、Raspberry Pi Picoを使ってスマートフォンで見られるウェブページを作り、公開します。このウェブページは「サーブ(配信)」といいます。
サーブされたウェブページには、同じWi-Fiに接続しているスマートフォンや、同じモバイルホットスポットに接続したスマートフォンだけがアクセスできます。
この仕組みを作ることで、スマートフォンを使った簡単なIoT(モノのインターネット)の仕組みを実現できます。
Webページを構成する3つの言語
Webページを作る際に主に使われる言語は次の3つです:
- HTML (.html):
Webページの基本となる「骨組み」を作ります。文字や画像、見出しなどを配置するための最低限の情報を記述します。 - CSS (.css):
Webページを装飾するための言語です。文字の色や背景、レイアウトの調整などを行います。 - JavaScript (.js):
Webページを動的に動かすための言語です。例えば、ボタンをクリックしたときに動作させたり、外部のデータとやり取りをしたりします。
ファイルの分け方
CSSやJavaScriptの内容はHTMLファイルに直接書くこともできます。ただし、HTMLの内容が増えると見やすさが悪くなる(可読性が下がる)ため、通常は別々のファイルとして保存します。今回は内容が少ないので、HTMLファイルだけを使います。
HTMLの基本構造
以下の例のようにHTMLファイルを作成し、ブラウザで開いてみましょう。ファイル名は自由ですが、拡張子を「.html」にする必要があります。ここでは「template.html」という名前にします。
<!doctype html> <!-- このファイルがHTML形式であることを示す -->
<html lang="ja"> <!-- 使用する言語を指定(ここでは日本語) -->
<head>
<meta charset="UTF-8"> <!-- 文字コードの指定(UTF-8推奨) -->
<title>タブのタイトル名</title> <!-- ブラウザタブに表示されるタイトル -->
<meta name="description" content="このページの簡単な説明"> <!-- ページの説明文 -->
</head>
<body>
<h1>見出し1</h1> <!-- 一番大きな見出し -->
<h2>見出し2</h2> <!-- 次に大きな見出し -->
<p>ここは普通のテキストです。段落として表示されます。</p> <!-- 段落 -->
<ul> <!-- 番号なしの箇条書き -->
<li>項目1</li>
<li>項目2</li>
</ul>
<ol> <!-- 番号ありの箇条書き -->
<li>1つ目の項目</li>
<li>2つ目の項目</li>
</ol>
<strong>太字のテキスト</strong> <!-- 太字にする -->
<br> <!-- 改行 -->
<img src="example.jpg" alt="画像の説明" style="width:100px; height:100px;"> <!-- 画像を表示 -->
</body>
</html>
ファイルを保存してブラウザで開く方法
- 上記のコードをテキストエディタ(例えばメモ帳やVSCode)にコピーします。
- 任意の名前(例:
template.html)で保存します。 - ファイルをダブルクリックすると、ブラウザで表示されます。
コードのポイント
<head>タグ内にはページの設定を記載します。<body>タグ内には実際に表示される内容を書きます。<h1>から<h6>は見出しを表し、数字が小さいほど大きな文字になります。<p>は段落を、<ul>や<ol>は箇条書きを表します。<img>は画像を挿入するためのタグです。
では実際にRaspberry Pi PicoでWebページを表示してみます。
以下のように、HTMLコード生成部分(webpage関数)を修正し、LEDの状態(ON/OFF)を操作できるWebサーバーを完成させます。
加えて、クライアントからのリクエストに応じてLEDを操作する処理をserve関数に追加しました。
import network
import socket
import machine
from time import sleep
# ==== Wi-Fi設定 ====
ssid = 'AAAAAAAA'
password = 'AAAAAAAA'
# ==== GPIO / センサー ====
led = machine.Pin("LED", machine.Pin.OUT) # Pico W内蔵LED
sensor = machine.ADC(4) # 内蔵温度センサー
conversion_factor = 3.3 / 65535.0 # ADC → 電圧変換
def read_temperature():
v = sensor.read_u16() * conversion_factor
return round(27 - (v - 0.706) / 0.001721, 2)
# ==== Wi-Fi接続 ====
wlan = None
def connect():
global wlan
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
while not wlan.isconnected():
sleep(0.3)
ip = wlan.ifconfig()[0]
print("Connected on", ip)
return ip
def wifi_off():
try:
if wlan:
wlan.disconnect()
wlan.active(False)
except:
pass
# ==== ソケット ====
def open_socket(ip):
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((ip, 80))
s.listen(1)
s.settimeout(0.5)
return s
# ==== HTML(最小構成) ====
def webpage(temp, state):
return f"""<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>PicoW</title></head>
<body style="text-align:center;font-family:sans-serif">
<h2>Pico W LED Control</h2>
<form action="/lighton"><input type="submit" value="ON"></form>
<form action="/lightoff"><input type="submit" value="OFF"></form>
<p>LED: <b>{state}</b></p>
<p>Temp: {temp} °C</p>
</body></html>"""
# ==== Webサーバ ====
def serve(connection):
print("HTTP server started")
state = "OFF"
led.off()
while True:
try:
try:
client, addr = connection.accept()
except OSError:
machine.idle()
continue
req = client.recv(512).decode("utf-8", "ignore")
first = req.split("\r\n", 1)[0]
print(first)
if "GET /lighton" in first:
led.on()
state = "ON"
elif "GET /lightoff" in first:
led.off()
state = "OFF"
temp = read_temperature()
html = webpage(temp, state)
client.send("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n")
client.sendall(html)
client.close()
except KeyboardInterrupt:
break
except Exception:
try:
client.close()
except:
pass
connection.close()
wifi_off()
led.off()
print("Server stopped")
# ==== 実行 ====
try:
ip = connect()
s = open_socket(ip)
serve(s)
except KeyboardInterrupt:
wifi_off()
led.off()
print("Stopped by user")実行方法
- コードを保存してRaspberry Pi Picoにアップロードします。
- Wi-Fi接続が成功するとIPアドレスが表示されるので、ブラウザでそのアドレスにアクセスします。
- 表示されたページで「Light on」「Light off」をクリックしてLEDを制御できます。
これで、Raspberry Pi Picoを使った基本的なLチカWebアプリが完成です。
要点と機能の説明
1. Wi-Fiに接続する (connect() 関数)
- PicoをWi-Fiに接続し、成功するとIPアドレスを取得します。
- このIPアドレスをブラウザで開くと、Webページが表示されるようになります。
def connect():
wlan = network.WLAN(network.STA_IF) # Wi-Fiのインターフェースを有効化
wlan.active(True)
wlan.connect(ssid, password) # 指定したSSIDとパスワードで接続
while not wlan.isconnected(): # 接続が確立するまで待機
print('Waiting for connection...')
sleep(1)
ip = wlan.ifconfig()[0] # 接続後のIPアドレスを取得
print(f'Connected on {ip}')
return ip2. ソケット(Webサーバー)を開く (open_socket() 関数)
- Raspberry Pi Picoが HTTPのポート(80番)でWebサーバーを立ち上げる 役割をします。
connection.listen(1)で、1つのクライアント(ブラウザなど)の接続を待ちます。
def open_socket(ip):
address = (ip, 80) # HTTPのポート(80番)で待機
connection = socket.socket() # ソケットを作成
connection.bind(address) # IPアドレスとポートをバインド
connection.listen(1) # クライアントの接続を待機
return connection3. 温度センサーのデータを取得 (read_temperature() 関数)
- Picoの内蔵温度センサー から値を取得し、摂氏(°C)に変換します。
- 温度は27°Cを基準 として、Picoのデータシートの式を使って計算されます。
def read_temperature():
voltage = sensor.read_u16() * conversion_factor # ADCの値を電圧に変換
temperature = 27 - (voltage - 0.706) / 0.001721 # 摂氏温度に変換
return round(temperature, 2) # 小数点2桁で返す4. HTMLページを生成 (webpage() 関数)
- WebページのHTMLを作成 し、現在のLEDの状態(ON/OFF)や 温度を表示 します。
- 「Light on」「Light off」ボタンをクリックすると、LEDの制御が可能 です。
def webpage(temperature, state):
html = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>RPi Pico LED Control</title>
</head>
<body>
<h1>Raspberry Pi Pico LED Control</h1>
<form action="./lighton">
<input type="submit" value="Light on" />
</form>
<form action="./lightoff">
<input type="submit" value="Light off" />
</form>
<p>LED is currently: <strong>{state}</strong></p>
<p>Temperature: {temperature} °C</p>
</body>
</html>
"""
return str(html)5. Webサーバーの処理 (serve() 関数)
- ブラウザからリクエスト(
/lightonまたは/lightoff)を受け取る と、LEDのオン・オフを切り替えます。 - その後、最新のLEDの状態と温度を反映したWebページを返します。
- ブラウザをリロードすると、最新の状態が表示されます。
def serve(connection):
state = 'OFF' # 初期状態はOFF
led.off() # LEDを消灯
while True:
client = connection.accept()[0] # クライアントの接続を受け付け
request = client.recv(1024).decode('utf-8') # HTTPリクエストを受け取る
print(request) # デバッグ用にリクエストを表示
# LEDの制御
if '/lighton' in request:
led.on()
state = 'ON'
elif '/lightoff' in request:
led.off()
state = 'OFF'
temperature = read_temperature() # 温度を取得
# クライアントにレスポンスを送信(HTMLページの更新)
response = webpage(temperature, state)
client.send('HTTP/1.1 200 OK\n')
client.send('Content-Type: text/html\n')
client.send('Connection: close\n\n')
client.sendall(response)
client.close()6.メイン処理(実行部分)
- PicoがWi-Fiに接続し、Webサーバーを立ち上げ、クライアント(ブラウザ)からの操作を待ちます。
Ctrl + Cでプログラムを中断すると、自動でPicoをリセットします。
try:
ip = connect() # Wi-Fi接続
connection = open_socket(ip) # Webサーバー開始
serve(connection) # クライアントのリクエスト処理
except KeyboardInterrupt:
machine.reset() # キーボードで中断されたらPicoをリセット課題8
Raspberry Pi PicoのLEDを、テキストボックスで指定した秒数だけ点灯させるプログラムに改造してください。
ヒント
HTMLフォームを修正
- ボタンの近くに入力フィールド(テキストボックス)を追加します。
durationという名前で点灯時間を送信します。
<form action="./lighton">
<input type="number" name="duration" placeholder="秒数" min="1">
<input type="submit" value="Light on">
</form>リクエストから秒数を取得
- Python側でリクエストを解析し、URL内の
durationパラメータを抽出します。
if '/lighton' in request:
# 点灯時間を取得(デフォルト: 0秒)
duration = int(request.split('duration=')[1].split(' ')[0])
led.on()
sleep(duration)
led.off()注意点
- ユーザーが正しい秒数を入力しなかった場合(負の値や空白など)、デフォルト値(例: 0秒)を設定する。
- 点灯中に新しいリクエストが来た場合の挙動をどうするか考える。
解答例はこちら
import network
import socket
import machine
from time import sleep, ticks_ms, ticks_diff
# ==== Wi-Fi設定 ====
ssid = 'AAAAAAAA'
password = 'AAAAAAAA'
# ==== GPIO ====
led = machine.Pin("LED", machine.Pin.OUT)
sensor = machine.ADC(4)
conversion_factor = 3.3 / 65535.0
# ==== 温度読み取り ====
def read_temperature():
voltage = sensor.read_u16() * conversion_factor
return round(27 - (voltage - 0.706) / 0.001721, 2)
# ==== Wi-Fi ====
wlan = None
def connect():
global wlan
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
while not wlan.isconnected():
sleep(0.3)
ip = wlan.ifconfig()[0]
print('Connected on', ip)
return ip
def wifi_off():
try:
if wlan:
wlan.disconnect()
wlan.active(False)
except:
pass
# ==== ソケット ====
def open_socket(ip):
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((ip, 80))
s.listen(1)
s.settimeout(0.5)
return s
# ==== シンプルHTML ====
def webpage(temp, state):
return f"""<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>PicoW</title></head>
<body style="text-align:center;font-family:sans-serif">
<h2>Pico W LED Control</h2>
<form action="/lighton"><input type="number" name="duration" min="1" max="300" placeholder="秒数">
<input type="submit" value="ON"></form>
<form action="/lightoff"><input type="submit" value="OFF"></form>
<form action="/shutdown"><input type="submit" value="STOP"></form>
<p>LED: <b>{state}</b></p>
<p>Temp: {temp} °C</p>
</body></html>"""
# ==== duration取得 ====
def parse_duration(first_line, default=5):
try:
path = first_line.split(" ")[1]
if "duration=" in path:
val = int(path.split("duration=")[1].split("&")[0].split(" ")[0])
return max(1, min(val, 300))
except:
pass
return default
# ==== Webサーバ ====
def serve(connection):
state = 'OFF'
led.off()
led_until = 0
while True:
if state == 'ON' and ticks_diff(led_until, ticks_ms()) <= 0:
led.off()
state = 'OFF'
try:
try:
client, addr = connection.accept()
except OSError:
machine.idle()
continue
req = client.recv(512).decode('utf-8', 'ignore')
first_line = req.split('\r\n', 1)[0]
print(first_line)
if 'GET /lighton' in first_line:
duration = parse_duration(first_line)
led.on()
state = 'ON'
led_until = ticks_ms() + duration * 1000
elif 'GET /lightoff' in first_line:
led.off()
state = 'OFF'
elif 'GET /shutdown' in first_line:
led.off()
client.send('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<h1>Stopped</h1>')
client.close()
break
temp = read_temperature()
html = webpage(temp, state)
client.send('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n')
client.sendall(html)
client.close()
except KeyboardInterrupt:
break
except Exception:
try:
client.close()
except:
pass
connection.close()
wifi_off()
led.off()
print("Server stopped")
# ==== 実行 ====
try:
ip = connect()
s = open_socket(ip)
serve(s)
except KeyboardInterrupt:
led.off()
wifi_off()
print("Stopped by user")Raspberry pi pico Wのファームウェアリセット
複雑なプログラムを記載していくと、バグでRaspberry pi picoがビジー状態になり、PCから接続できなくなる場合があります。
書き込んだmain.pyを一度消さないと、電源を入れる限り自動で実行され、永久にアクセスできなくなってしまいます。
そこでフラッシュメモリをリセットし、出荷時の状態に戻す方法を紹介しておきます。
公式サイトにUF2ファイルが公開されています。
https://www.raspberrypi.com/documentation/microcontrollers/pico-series.html#software-utilities
ページ下部にある、 Resetting Flash memoryからUF2File をダウンロードします。

Raspberry Pi Pico Wにあるプッシュスイッチを押しながらパソコンに接続します。

ダウンロードしたファイルをドラッグ&ドロップします。

少し待つと初期化されますので、再度Raspberry Pi Pico W用の専用UF2ファイルをダウンロードしてください。
こちらの記事で解説しています。
ロボットを走らせる
それではWebページからロボットを操作できるようにしてみます。
以下のようなモータ関係の関数を追加します。
# モーターのピン設定
motorL_plus = PWM(Pin(0))
motorL_minus = PWM(Pin(1))
motorR_plus = PWM(Pin(2))
motorR_minus = PWM(Pin(3))
# PWMの周波数を設定
motorL_plus.freq(1000)
motorL_minus.freq(1000)
motorR_plus.freq(1000)
motorR_minus.freq(1000)
# 16ビットの最大値(フルパワー)
duty_common = 65535
# ロボットの移動関数
def forward(duty):
motorL_plus.duty_u16(duty)
motorL_minus.duty_u16(0)
motorR_plus.duty_u16(duty)
motorR_minus.duty_u16(0)
def back(duty):
motorL_plus.duty_u16(0)
motorL_minus.duty_u16(duty)
motorR_plus.duty_u16(0)
motorR_minus.duty_u16(duty)
def stop():
motorL_plus.duty_u16(0)
motorL_minus.duty_u16(0)
motorR_plus.duty_u16(0)
motorR_minus.duty_u16(0)Web上のボタンで前進、後退、停止が出来るプログラムです。
import network
import socket
from time import sleep
import machine
# ==== 設定 ====
ssid = 'AAAAAAAA'
password = 'AAAAAAA'
duty_common = 40000 # 約60%
# ==== PWM設定 ====
motorL_plus = machine.PWM(machine.Pin(0))
motorL_minus = machine.PWM(machine.Pin(1))
motorR_plus = machine.PWM(machine.Pin(2))
motorR_minus = machine.PWM(machine.Pin(3))
for p in (motorL_plus, motorL_minus, motorR_plus, motorR_minus):
p.freq(1000)
def forward(duty):
motorL_plus.duty_u16(duty); motorL_minus.duty_u16(0)
motorR_plus.duty_u16(duty); motorR_minus.duty_u16(0)
def back(duty):
motorL_plus.duty_u16(0); motorL_minus.duty_u16(duty)
motorR_plus.duty_u16(0); motorR_minus.duty_u16(duty)
def stop():
motorL_plus.duty_u16(0); motorL_minus.duty_u16(0)
motorR_plus.duty_u16(0); motorR_minus.duty_u16(0)
def deinit_pwms():
for p in (motorL_plus, motorL_minus, motorR_plus, motorR_minus):
try:
p.deinit()
except:
pass
# ==== Wi-Fi ====
wlan = None
def connect():
global wlan
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
while not wlan.isconnected():
sleep(0.3)
ip = wlan.ifconfig()[0]
print('Connected on', ip)
return ip
def wifi_off():
try:
if wlan:
wlan.disconnect()
wlan.active(False)
except:
pass
# ==== HTTP ====
def open_socket(ip):
address = (ip, 80)
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(address)
s.listen(1)
s.settimeout(0.5) # ★ acceptをブロックしない
return s
def webpage():
return """<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Robot Control</title>
<style>
body { text-align:center; font-family:Arial,sans-serif; background:#f4f4f4; }
h1 { color:#333; }
.container { display:grid; grid-template-columns:1fr; gap:10px; justify-content:center; max-width:400px; margin:auto; }
button { width:100%; height:100px; font-size:30px; font-weight:bold; border-radius:10px; border:none; background:#007BFF; color:#fff; }
button:hover { background:#0056b3; }
.stop-button { background:red; }
.stop-button:hover { background:darkred; }
.note { margin-top:16px; color:#555; font-size:14px; }
</style>
</head>
<body>
<h1>Robot Control</h1>
<div class="container">
<form action="/forward"><button>⬆️ Forward</button></form>
<form action="/stop"><button class="stop-button">⏹ Stop</button></form>
<form action="/backward"><button>⬇️ Backward</button></form>
<form action="/shutdown"><button>🛑 Shutdown Server</button></form>
</div>
<p class="note">Use /shutdown to safely stop the server.</p>
</body>
</html>"""
def serve(connection):
print("HTTP server started")
while True:
try:
try:
client, addr = connection.accept() # タイムアウト0.5s
except OSError:
# タイムアウト → REPLに制御を返しつつ継続
machine.idle()
continue
client.settimeout(2)
req = client.recv(512) # ★読み過ぎない(512byte)
try:
request = req.decode('utf-8', errors='ignore')
except:
request = str(req)
# ログは最初の1行だけ(大量出力を避ける)
first_line = request.split('\r\n', 1)[0]
print(first_line)
# ルーティング
if 'GET /forward' in request:
forward(duty_common)
elif 'GET /backward' in request:
back(duty_common)
elif 'GET /stop' in request:
stop()
elif 'GET /shutdown' in request:
stop()
html = "<h1>Server shutting down...</h1>"
client.send('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n')
client.sendall(html)
client.close()
break # ループ脱出 → 後始末へ
# レスポンス
html = webpage()
client.send('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n')
client.sendall(html)
client.close()
except KeyboardInterrupt:
print("KeyboardInterrupt")
break
except Exception as e:
# 通信途中の例外は握りつぶして継続
try:
client.close()
except:
pass
# 後始末
try:
connection.close()
except:
pass
stop()
deinit_pwms()
wifi_off()
print("Server stopped")
# ==== 起動 ====
try:
# 開発時の“逃げ”のための猶予(必要なら1〜2秒でOK)
# sleep(2)
ip = connect()
s = open_socket(ip)
serve(s)
except KeyboardInterrupt:
stop(); deinit_pwms(); wifi_off()
print("Interrupted and cleaned up.")課題9
左右の旋回動作もできるようにボタンを追加してください。
課題10
CSSを使って操縦しやすいインターフェイスを作ってください。
時間の許す限りで機能を追加してください。
(PWMの速度指定、LED点灯機能、温度表示機能、距離センサ表示機能等)
例はこちら
import network
import socket
from time import sleep
import machine
# Wi-FiのSSIDとパスワード
ssid = 'AAAAAAAAAA'
password = 'AAAAAAAAAA'
# モーター制御用のGPIOピン設定
motorL_plus = machine.PWM(machine.Pin(0))
motorL_minus = machine.PWM(machine.Pin(1))
motorR_plus = machine.PWM(machine.Pin(2))
motorR_minus = machine.PWM(machine.Pin(3))
# PWMの周波数を設定
motorL_plus.freq(1000)
motorL_minus.freq(1000)
motorR_plus.freq(1000)
motorR_minus.freq(1000)
# 16ビットの最大値(フルパワー)
duty_common = 40000 # デフォルトのデューティ比(約60%)
# ロボットの移動関数
def forward(duty):
motorL_plus.duty_u16(duty)
motorL_minus.duty_u16(0)
motorR_plus.duty_u16(duty)
motorR_minus.duty_u16(0)
def back(duty):
motorL_plus.duty_u16(0)
motorL_minus.duty_u16(duty)
motorR_plus.duty_u16(0)
motorR_minus.duty_u16(duty)
def stop():
motorL_plus.duty_u16(0)
motorL_minus.duty_u16(0)
motorR_plus.duty_u16(0)
motorR_minus.duty_u16(0)
# Wi-Fi接続
def connect():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
while not wlan.isconnected():
print('Waiting for connection...')
sleep(1)
ip = wlan.ifconfig()[0]
print(f'Connected on {ip}')
return ip
# ソケットを開く
def open_socket(ip):
address = (ip, 80)
connection = socket.socket()
connection.bind(address)
connection.listen(1)
return connection
# HTMLページの生成(ボタンを大きくし、スマホ対応)
def webpage():
html = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Robot Control</title>
<style>
body { text-align: center; font-family: Arial, sans-serif; background-color: #f4f4f4; }
h1 { color: #333; }
.container { display: grid; grid-template-columns: 1fr; gap: 10px; justify-content: center; max-width: 400px; margin: auto; }
.full-width { width: 100%; }
button {
width: 100%; height: 100px; font-size: 30px; font-weight: bold;
border-radius: 10px; border: none; background-color: #007BFF; color: white;
}
button:hover { background-color: #0056b3; }
.stop-button { background-color: red; }
.stop-button:hover { background-color: darkred; }
</style>
</head>
<body>
<h1>Robot Control</h1>
<div class="container">
<form action="./forward"><button class="full-width">⬆️ Forward</button></form>
<form action="./stop"><button class="stop-button full-width">⏹ Stop</button></form>
<form action="./backward"><button class="full-width">⬇️ Backward</button></form>
</div>
</body>
</html>
"""
return str(html)
# Webサーバーの処理
def serve(connection):
while True:
client = connection.accept()[0]
request = client.recv(1024).decode('utf-8')
print(request)
# モーターの制御
if '/forward' in request:
print("Robot moving forward")
forward(duty_common)
elif '/backward' in request:
print("Robot moving backward")
back(duty_common)
elif '/stop' in request:
print("Robot stopped")
stop()
# Webページを更新
response = webpage()
client.send('HTTP/1.1 200 OK\n')
client.send('Content-Type: text/html\n')
client.send('Connection: close\n\n')
client.sendall(response)
client.close()
# 実行部分
try:
ip = connect()
connection = open_socket(ip)
serve(connection)
except KeyboardInterrupt:
stop() # 緊急停止
machine.reset()import network
import socket
from time import sleep, ticks_ms, ticks_diff
import machine
# ===== Wi-Fi設定 =====
ssid = 'AAAAAAAAAA'
password = 'AAAAAAAAAA'
# ===== PWM / Motor =====
motorL_plus = machine.PWM(machine.Pin(0))
motorL_minus = machine.PWM(machine.Pin(1))
motorR_plus = machine.PWM(machine.Pin(2))
motorR_minus = machine.PWM(machine.Pin(3))
for p in (motorL_plus, motorL_minus, motorR_plus, motorR_minus):
p.freq(1000)
duty_common = 40000 # 約60%
def forward(duty):
motorL_plus.duty_u16(duty); motorL_minus.duty_u16(0)
motorR_plus.duty_u16(duty); motorR_minus.duty_u16(0)
def back(duty):
motorL_plus.duty_u16(0); motorL_minus.duty_u16(duty)
motorR_plus.duty_u16(0); motorR_minus.duty_u16(duty)
def stop():
motorL_plus.duty_u16(0); motorL_minus.duty_u16(0)
motorR_plus.duty_u16(0); motorR_minus.duty_u16(0)
def deinit_pwms():
for p in (motorL_plus, motorL_minus, motorR_plus, motorR_minus):
try:
p.deinit()
except:
pass
# ===== Wi-Fi =====
wlan = None
CONNECT_TIMEOUT_MS = 10_000 # 10秒で諦める
def connect():
"""成功: IPを返す / 失敗: None を返す(サーバは起動しない)"""
global wlan
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
try:
wlan.disconnect()
except:
pass
wlan.connect(ssid, password)
deadline = ticks_ms() + CONNECT_TIMEOUT_MS
last_log = 0
while not wlan.isconnected():
if ticks_diff(ticks_ms(), deadline) > 0:
print("Wi-Fi connect timeout. Check SSID/PASS and run again.")
return None
if ticks_diff(ticks_ms(), last_log) > 1000:
print("Connecting...")
last_log = ticks_ms()
machine.idle() # CPUを譲る
ip = wlan.ifconfig()[0]
print(f'Connected on {ip}')
return ip
def wifi_off():
try:
if wlan:
wlan.disconnect()
wlan.active(False)
except:
pass
# ===== ソケット =====
def open_socket(ip):
address = (ip, 80)
s = socket.socket()
try:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
except:
pass
s.bind(address)
s.listen(1)
s.settimeout(0.5) # accept をブロックしすぎない
return s
# ===== HTML(元のまま) =====
def webpage():
html = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Robot Control</title>
<style>
body { text-align: center; font-family: Arial, sans-serif; background-color: #f4f4f4; }
h1 { color: #333; }
.container { display: grid; grid-template-columns: 1fr; gap: 10px; justify-content: center; max-width: 400px; margin: auto; }
.full-width { width: 100%; }
button {
width: 100%; height: 100px; font-size: 30px; font-weight: bold;
border-radius: 10px; border: none; background-color: #007BFF; color: white;
}
button:hover { background-color: #0056b3; }
.stop-button { background-color: red; }
.stop-button:hover { background-color: darkred; }
</style>
</head>
<body>
<h1>Robot Control</h1>
<div class="container">
<form action="./forward"><button class="full-width">⬆️ Forward</button></form>
<form action="./stop"><button class="stop-button full-width">⏹ Stop</button></form>
<form action="./backward"><button class="full-width">⬇️ Backward</button></form>
</div>
</body>
</html>
"""
return str(html)
# ===== Webサーバ =====
def serve(connection):
print("HTTP server started")
while True:
try:
try:
client, addr = connection.accept() # 0.5sで抜ける
except OSError:
machine.idle()
continue
try:
client.settimeout(2)
except:
pass
req_raw = client.recv(512) # 取りすぎない
try:
request = req_raw.decode('utf-8', 'ignore')
except:
request = str(req_raw)
# ログは先頭1行のみ(大量出力を避ける)
first_line = request.split('\r\n', 1)[0]
print(first_line)
# モーター制御
if 'GET /forward' in first_line:
forward(duty_common)
elif 'GET /backward' in first_line:
back(duty_common)
elif 'GET /stop' in first_line:
stop()
elif 'GET /shutdown' in first_line:
# HTMLは変更せず、URL直叩き用の安全停止
stop()
try:
client.send('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n<h1>Server shutting down...</h1>')
except:
pass
try:
client.close()
except:
pass
break # 後始末へ
# レスポンス
html = webpage()
try:
client.send('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n')
client.sendall(html)
except:
pass
try:
client.close()
except:
pass
except KeyboardInterrupt:
print("KeyboardInterrupt")
break
except Exception:
# 通信途中の例外は握りつぶして継続
try:
client.close()
except:
pass
# ===== 後始末 =====
try:
connection.close()
except:
pass
stop()
deinit_pwms()
wifi_off()
print("Server stopped")
# ===== 実行 =====
try:
ip = connect()
if ip is not None:
connection = open_socket(ip)
serve(connection)
else:
# Wi-Fi失敗:安全終了(REPLは生きている)
stop()
deinit_pwms()
wifi_off()
print("Server not started (Wi-Fi failed). Fix credentials and run again.")
except KeyboardInterrupt:
stop()
deinit_pwms()
wifi_off()
print("Interrupted and cleaned up.")
machine.reset()
