見出し画像

【Raspberry Pi Pico W】無線通信(Wi-Fi)のお試しサンプルコード集

0.はじめに

細かい説明などは他の記事を参考にしていただけると幸いです。
(私はネットワーク系の知識が乏しいので、うまく説明できないので…)
そのため、とりあえず動かしてみた系のサンプルコードを記載しておきます

  • micropython : v1.20

Raspberry Pi Pico Wのmicropythonバージョンの確認

1.1 AccessPointとして使う(APモード)

変数SSIDとPASSWORDは任意の文字列を指定可能
※テスト用のため、ソースコードに直書きしてます(あまりよくない)

import network
import gc

# DEFINE
SSID:str = 'PICO_PICO_HAMMER'  # 任意のアクセスポイント名を指定可能
PASSWORD:str = 'qwertyuiop'  # 任意のパスワードを指定可能

gc.collect()  # garbage collection(ゴミ掃除)

# アクセスポイントを作成し、access_pointを返す
access_point = network.WLAN(network.AP_IF)  # AP:Access Pointとして使う
access_point.config(essid=SSID, password=PASSWORD)  # APの設定を反映する
access_point.ifconfig(('192.168.4.3', '255.255.255.0', '192.168.4.1', '192.168.4.2'))  # 各種IPアドレスを指定する(固定)

access_point.active(True)  # アクセスポイントとして検知されるようになる

# 接続が成功した場合、(IPアドレス、ネットマスク、ゲートウェイ、DNS)を表示
print("Please connection access_point by other device...")
print("IP Address:{}\nNet Mask:{}\nGateway:{}\nDNS:{}".format(*access_point.ifconfig()))


while True:
    pass  # 特に処理はしない

実行結果

実行するとWi-FIのアクセスポイントとして検出でき、passwordを入力すれば接続可能
(当然ですが、外部にはアクセスできません)

1.2 AccessPointとして使い、遠隔でLEDのON/OFFを制御する(APモード)

import usocket as socket
import network
import gc

# DEFINE
SSID:str = 'PICO_PICO_HAMMER'  # 任意のアクセスポイント名を指定可能
PASSWORD:str = 'qwertyuiop'  # 任意のパスワードを指定可能

def make_access_point(wifi_ssid:str, wifi_password:str):
    """アクセスポイントを作成し、access_pointを返す関数"""
    access_point = network.WLAN(network.AP_IF)  # AP:Access Pointとして使う
    access_point.config(essid=wifi_ssid, password=wifi_password)  # APの設定を反映する
    access_point.ifconfig(('192.168.4.3', '255.255.255.0', '192.168.4.1', '192.168.4.2'))  # 各種IPアドレスを指定する(固定)
    access_point.active(True)  # アクセスポイントとして検知されるようになる

    # 接続が成功した場合、(IPアドレス、ネットマスク、ゲートウェイ、DNS)を表示
    print("Please connection access_point by other device...")
    print("IP Address:{}\nNet Mask:{}\nGateway:{}\nDNS:{}".format(*access_point.ifconfig()))

    return access_point

class MyHomepageServer():

    def __init__(self):
        # socketの構成、設定
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # socketオブジェクトを作成
        self.socket.bind(('', 80))  # bind
        self.socket.listen(5)  # listen

    def _make_html_text(self, show_message:str):
        """ レスポンスとして返すテキスト(html形式)を返す関数 """
        html = ('''<html>
                     <head>
                       <meta charset="utf-8">
                       <title>Raspberry Pi pico server</title>
                     </head>
                     <body>
                       <h1>Message : {}</h1>
                       <a href="/led/on">Turn On LED</a></br>
                       <a href="/led/off">Turn Off LED</a>
                     </body>
                   </html>'''.format(show_message))
        return html

    def receive_waiting(self):
        """
        受信(accept)
        str型に変換し、テキストによって処理を分岐
        1. b'GET /led/on'の指示が記載されている -> LEDを点灯し、messageに「LED is ON!」を埋め込み、htmlを送信(send)
        2. b'GET /led/off\r\n'の指示が記載されている -> LEDを消灯し、messageに「LED is Off」を埋め込み、htmlを送信(send)
        3. b'GET /favicon.ico HTTP/1.1\r\n'の指示が記載されている -> LEDを消灯し、messageに「GET favicon.ico」を埋め込み、htmlを送信(send)
        3. 上記(1,2,3)以外 -> messageに「Please click any hyperlinks.」の文字列を埋め込み、htmlを送信(send)

        たまに'GET /favicon.ico HTTP/1.1\r\n'の指示がacceptされるが、
        """
        tmp_connection, addr = self.socket.accept()  # 指示待ち状態に(指示あるまでココで一時停止状態となる)
        print(f'Got a connection from {addr}')  # アクセスして来た相手のAddressを表示
        received_text = tmp_connection.recv(1024)  # 受け取った内容を変数に格納(binary)
        print(f'=== Content ===\n{received_text}')  # 受け取った内容を表示(GET等)

        if b"GET /led/on" in received_text:
            led.value(1)  # 出力をHIGHにする(LEDが点灯)
            send_text = self._make_html_text("LED is ON!")
        elif b"GET /led/off" in received_text:
            led.value(0)  # 出力をlowにする(LEDが消灯)
            send_text = self._make_html_text("LED is Off")
        elif b"GET /favicon.ico" in received_text:
            send_text = self._make_html_text("GET favicon.ico")
        else:
            send_text = self._make_html_text("Please click any hyperlinks.")

        tmp_connection.send(send_text)  # 応答(response)を、接続相手に返す
        tmp_connection.close()  # connectionを閉じる

        return None


# ココからmain文
gc.collect()  # garbage collection(ゴミ掃除)
led = machine.Pin("LED", machine.Pin.OUT)  # LED点灯用のPINを設定
ap = make_access_point(SSID, PASSWORD)  # 関数にまとめてある

server = MyHomepageServer() # クラスを定義

while True:
    server.receive_waiting()  # こちらも関数にまとめてある

実行結果

PCやスマホで192.168.4.3にアクセスし、URLをクリックすると、picoのLEDを点灯/消灯 できる。
※たまに192.168.4.1になってしまうがなぜなのかは不明…(要調査)
 おそらく初回は192.168.4.3になっていて、2回目(Thonnyの実行ボタンを押下して実行)は192.168.4.1になるっぽいです…

192.168.4.3でアクセス成功した例
192.168.4.1でアクセスできた例
ちなみにモバイルバッテリーからmicroUSB経由で電源供給可能
(この状態でもAccessPointとして接続できます)

2. 既存のAccessPointに接続する(STAモード?)

先ほどの1.1で作成したアクセスポイントに接続してみる

import time
import network
import socket
from machine import Pin
import gc

led = Pin("LED", machine.Pin.OUT)

# DEFINE
SSID:str = 'PICO_PICO_HAMMER'  # 任意のアクセスポイント名を指定可能
PASSWORD:str = 'qwertyuiop'  # 任意のパスワードを指定可能

def connect_wifi():
    wlan = network.WLAN(network.STA_IF)  # STAモードで定義
    wlan.active(True)
    wlan.connect(SSID, PASSWORD)  # 既存のアクセスポイントへ接続する
    wlan.ifconfig(('192.168.4.13', '255.255.255.0', '192.168.4.2', '192.168.4.1'))  # 192.168.4.1にIPアドレスを固定

    # Wait for connect or fail
    max_wait = 10
    while max_wait > 0:
        if wlan.status() < 0 or wlan.status() >= 3:
            break
        max_wait -= 1
        print('waiting for connection...')
        time.sleep(1)

    # Handle connection error
    if wlan.status() != 3:
        raise RuntimeError('network connection failed')
    else:
        print('Connected')
        status = wlan.ifconfig()
        print( 'ip = ' + status[0] )
    return wlan


class MyHomepageServer2():

    def __init__(self):
        # socketの構成、設定
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # socketオブジェクトを作成
        addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]  # 公式推奨のやり方
        self.socket.bind(addr)  # bind
        self.socket.listen(2)  # listen
        print('listening on', addr)

    def _make_html_text(self, show_message:str):
        """ レスポンスとして返すテキスト(html形式)を返す関数 """
        html = ('''<html>
                     <head>
                       <meta charset="utf-8">
                       <title>Raspberry Pi pico server</title>
                     </head>
                     <body>
                       <h1>Message : {}</h1>
                       <a href="/led/on">Turn On LED</a></br>
                       <a href="/led/off">Turn Off LED</a>
                     </body>
                   </html>'''.format(show_message))
        return html

    def receive_waiting(self):
        """
        受信(accept)
        str型に変換し、テキストによって処理を分岐
        1. b'GET /led/on'の指示が記載されている -> LEDを点灯し、messageに「LED is ON!」を埋め込み、htmlを送信(send)
        2. b'GET /led/off\r\n'の指示が記載されている -> LEDを消灯し、messageに「LED is Off」を埋め込み、htmlを送信(send)
        3. b'GET /favicon.ico HTTP/1.1\r\n'の指示が記載されている -> LEDを消灯し、messageに「GET favicon.ico」を埋め込み、htmlを送信(send)
        3. 上記(1,2,3)以外 -> messageに「Please click any hyperlinks.」の文字列を埋め込み、htmlを送信(send)

        たまに'GET /favicon.ico HTTP/1.1\r\n'の指示がacceptされるが、
        """
        tmp_connection, addr = self.socket.accept()  # 指示待ち状態に(指示あるまでココで一時停止状態となる)
        print(f'Got a connection from {addr}')  # アクセスして来た相手のAddressを表示
        received_text = tmp_connection.recv(1024)  # 受け取った内容を変数に格納(binary)
        print(f'=== Content ===\n{received_text}')  # 受け取った内容を表示(GET等)

        if b"GET /led/on" in received_text:
            led.value(1)  # 出力をHIGHにする(LEDが点灯)
            send_text = self._make_html_text("LED is ON!")
        elif b"GET /led/off" in received_text:
            led.value(0)  # 出力をlowにする(LEDが消灯)
            send_text = self._make_html_text("LED is Off")
        elif b"GET /favicon.ico" in received_text:
            send_text = self._make_html_text("GET favicon.ico")
        else:
            send_text = self._make_html_text("Please click any hyperlinks.")

        tmp_connection.send(send_text)  # 応答(response)を、接続相手に返す
        tmp_connection.close()  # connectionを閉じる

        return None

gc.collect()  # garbage collection(ゴミ掃除)
wlan = connect_wifi()  # Wi-Fiに接続する
server = MyHomepageServer2() # クラスを定義

while True:
    server.receive_waiting()  # こちらも関数にまとめてある

実行結果

接続できるとこのpicoのipアドレスが表示される
192.168.4.13にアクセスすれば、1.2と同様にLEDの点灯/消灯ができる
左側:②2.のコードを書き込んだpico W
右側:①1.2のコードを書き込んだpico W(AccessPoint)
①経由で②(192.168.4.13)に接続し、LEDの点灯/消灯 をPCから制御している

3. picoからpicoへ指示を出す(urequests)

先ほどまではPCブラウザなどでURLにアクセスすることで制御していたが、micropythonのurequestでURLにアクセスすることで、picoから制御することが可能

import utime as time
import network
from machine import Pin
import urequests as requests

# DEFINE
SSID:str = 'PICO_PICO_HAMMER'  # 任意のアクセスポイント名を指定可能
PASSWORD:str = 'qwertyuiop'  # 任意のパスワードを指定可能
URL_LED_ON:str = "http://192.168.4.13/led/on"
URL_LED_OFF:str = "http://192.168.4.13/led/off"

def l_chika(led, n=3):
    for _ in range(n):
        led.on()
        time.sleep(0.1)
        led.off()
        time.sleep(0.1)
    return None

def send_message_led_on():
    """  """
    try:
        response = requests.get(URL_LED_ON)  # requestを送る(LED点灯の指示)
        print(response.status_code) # 基本200
        print(response.text)  # 受信内容を表示
    except:
        """ おそらくurllib3が想定しているHTMLフォーマットでないため、エラーが発生する(指示は通っている)
        urllib3.exceptions.ProtocolError: 
        ('Connection aborted.', BadStatusLine('<html><body><h1>Message : LED is Off</h1><a href="/on">Turn On LED</a></br>\n'))
        """
        pass
    return None

def send_message_led_off():
    """  """
    try:
        requests.get(URL_LED_OFF)  # requestを送る(LED消灯の指示)
        print(response.status_code) # 基本200
        print(response.text)  # 受信内容を表示
    except:
        pass
    return None

def connect_wifi():
    wlan = network.WLAN(network.STA_IF)  # STAモードで定義
    wlan.active(True)
    wlan.connect(SSID, PASSWORD)  # 既存のアクセスポイントへ接続する
    # wlan.ifconfig(('192.168.4.23', '255.255.255.0', '192.168.4.2', '192.168.4.1'))  # 192.168.4.39にIPアドレスを固定

    # Wait for connect or fail
    max_wait = 10
    while max_wait > 0:
        if wlan.status() < 0 or wlan.status() >= 3:
            break
        max_wait -= 1
        print('waiting for connection...')
        time.sleep(1)

    # Handle connection error
    if wlan.status() != 3:
        raise RuntimeError('network connection failed')
    else:
        print('Connected')
        status = wlan.ifconfig()
        print( 'ip = ' + status[0] )
    return wlan


led = Pin("LED", machine.Pin.OUT)
l_chika(led, n=3)  # LEDを3回点滅させる

wlan = connect_wifi()  # Wi-Fiに接続する

while True:
    time.sleep(0.1)  # 0.1秒スリープする
    if rp2.bootsel_button() == 1:  # bootselボタンが押下状態の場合
        send_message_led_on()  # requestを送る(LED点灯の指示)
        l_chika(led, n=2)  # LEDを2回点滅させる
        time.sleep(1)  # 1秒スリープする
        send_message_led_off()  # requestを送る(LED消灯の指示)
        l_chika(led, n=1)  # LEDを1回点滅させる

実行結果

Thonnyの画面ではわからないが、picoのBOOTSELボタンを押下すると、
別のpicoのLEDが1秒点灯し、消灯する(下記画像参照のこと)
左側:③3のコードを書き込みしたpico W(リクエスト送信用)
中央:①1.2のコードを書き込みしたpico W(AccessPointとして使用)
右側:②2のコードを書き込みしたpico W(リクエスト受取用)
③のBOOTSELボタンを押下すると、①経由で②(192.168.4.13)に指示が飛び、LEDが点灯/消灯する

最後に

Raspberry Pi pico Wだけで外部インターネットと隔離されたローカルネットワークが作れるため、意外なところで使える…かもしれないなぁ、と感じた

この記事が気に入ったらサポートをしてみませんか?