0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Web Serial APIとCDN版Vue.jsで作る、Raspberry Pi Pico WのWiFi設定ツールの実装例

Last updated at Posted at 2025-06-07

今回の内容

前回では、Web Serial APIを介してブラウザとRaspberry Pi Pico Wが通信するJavaScriptコードのクラス化と、CDN版Vue.jsをあえて用いたGUI側の制御を実装しました。
今回は実際に、Raspberry Pi Pico WのWiFi設定をPCのWebブラウザとUSBシリアル経由で行えるツールを、Vue.jsとWeb Serial APIで構築してみました。
設定情報は暗号化され、再設定操作も制限されるなど、実用を見据えた構成になっています。

Raspberry Pi Pico W側のMicroPythonコード

まず、Raspberry Pi Pico Wに以下の2つの機能を持たせたコードを実装します:

  • 引数として受け取ったWiFi接続情報を保存する
  • 保存された接続情報をもとにWiFiに接続する

このコードは boot.pymain.py ではありません。もちろん、それらから import されて使用されることは想定していますが、これはあくまで独立したモジュールとしての実装です。
wifisetting.py

このコードの主な機能は、まず引数として受け取ったWiFi情報を内部に保存することです。

def save_wifi_config(json_string, filename=_SETTING_FILE_NAME):
    """
    引数で指定された文字列をJSONとして解釈し、パスワードを暗号化してからファイルに保存
    """
    try:
        wifi_config = ujson.loads(json_string)
        key = generate_key()
        if "wifi_password" in wifi_config and wifi_config["wifi_password"]:
            encrypted_password = encrypt_password(wifi_config["wifi_password"], key)
            wifi_config["wifi_password_encrypted"] = encrypted_password
            del wifi_config["wifi_password"]  # 元のパスワードを削除

        with open(filename, "w") as f:
            ujson.dump(wifi_config, f)
        print(f"WiFi configuration saved to '{filename}'.")
        return True, wifi_config
    except Exception as e:
        print(f"Error saving WiFi config: {e}")
        return False, None

保存された情報は、以下の関数でWiFi接続に使用されます。

def apply_wifi_config(filename=_SETTING_FILE_NAME):
    """
    ファイルに保存されたWiFi接続情報を用いてWiFiに接続
    """
    try:
        if filename not in os.listdir():
            print(f"Config file '{filename}' not found.")
            return False

        config = ujson.loads(open(filename).read())
        wlan = network.WLAN(network.STA_IF)
        if wlan.isconnected():
            wlan.disconnect()
        print("Disconnect Wi-Fi...")
        wlan.active(True)

        ssid = config.get("wifi_ssid")
        encrypted_password = config.get("wifi_password_encrypted")
        use_static = config.get("use_static_ip", False)
        ip = config.get("static_ip")
        subnet = config.get("subnet_mask")
        gateway = config.get("gateway")
        dns = config.get("dns_server")

        password = None
        if encrypted_password:
            key = generate_key()
            try:
                password = decrypt_password(encrypted_password, key)
            except ValueError as e:
                print(f"Password decryption error: {e}")
                return

        if ssid and password is not None:
            if use_static:
                if ip and subnet and gateway and dns:
                    wlan.ifconfig((ip, subnet, gateway, dns))
                    print(f"Using static IP: {ip}, GW: {gateway}, Subnet: {subnet}, DNS: {dns}")
                else:
                    print("Static IP configuration incomplete.")
            else:
                print("Using DHCP.")

            wlan.connect(ssid, password)
            print(f"Connected to {ssid}.")
            print("IP Address:", wlan.ifconfig()[0])
            return True
        else:
            print("SSID or encrypted password not found in configuration.")
    except Exception as e:
        print(f"Error connecting WiFi: {e}")
    return False

この2つの処理そのものは特に目新しいものではありません。Qiitaにも多くの情報があるので、ぜひ他の方の記事も参照してみてください。

そのほか、以下のような付随機能も実装しています:

  • WiFi接続パスワードを、Raspberry Pi Pico Wの固有IDをキーとして暗号化
  • アクセス可能なWiFiのSSIDを検索し、電波強度を測定
  • 接続後、導通確認を兼ねてNTPサーバへアクセスし、内部の時計を同期

暗号化については、Raspberry Pi Pico Wの固有IDをそのままキーに使うと容易に解読されてしまう可能性があります。

そのため、実運用では次のような工夫を加えるとより安全です:

  • 固有IDにビットシフトをかける
  • 固定のプレフィックス/サフィックスを加える
  • .mpy にコンパイルして外部からの読み取りを困難にする

こうすることで、仮に機器が盗難されたとしてもWiFi情報の流出を防ぎやすくなります。

(おまけ)main.py

「このように使用されることを想定している」という意味で、main.py も記述してみました。
main.py
内容は非常にシンプルで、以下のような処理をしています:

  1. WiFi接続情報がなければ終了
  2. NTPサーバに接続して内部時計を同期
  3. 指定されたURLに1分ごとにアクセスし、結果をprintに出力
  4. ただし、5秒以内にBOOTSELボタンが3回押された場合は「リセット待機モード(LED点滅)」に移行し、この状態でさらに5秒以内に3回押された場合はWiFi接続情報を削除して再起動

今回の例では、何も接続していないRaspberry Pi Pico Wでも実行可能なようにBOOTSELボタンと本体のLEDのみを使用してみました。
※一応、コード内ではGPIO28にブザーが接続されている事を想定していますが、無くとも動作します
実運用では、GPIOにスライドスイッチやLED、ブザーを接続したりして制御する事が望ましいと思われます。
以上、よろしければ実際の実装の参考にしてみてください。

HTML側の実装

上記 wifisetting.py の関数を、Web Serial APIとREPLを介してブラウザから呼び出すHTMLおよびJavaScriptが以下になります:

注目していただきたいのは後ろの2つです。
前回 実装したJavaScriptコードを再利用しています。

<script type="importmap">
  {
    "imports": {
      "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js",
      "mitt": "https://cdn.jsdelivr.net/npm/mitt@3.0.1/+esm",
      "WebSerial": "./js/WebSerial.js",
      "VueWebSerial": "./js/VueWebSerial.js"
    }
  }
</script>

このように import 可能とすることで、前回のコードにある ConsoleLog, SentLog, ReceivedLog などの処理はそのまま活かしつつ、今回のWebページで必要な機能だけを追加しています。

<script type="module">
  import { createApp, ref } from "vue";
  import { vueWebSerial } from "./js/VueWebSerial.js";
  const app = createApp({
    setup() {
      const { ConsoleLog, SentLog, ReceivedLog, wasConnected, clickStart, clickClose, clickTest } = vueWebSerial();
      const wifiInfo = {
        SSID: "",
        password: "",
        useStaticIP: false,
        staticIP: [0, 0, 0, 0],
        subnetMask: [255, 255, 255, 0],
        gateway: [0, 0, 0, 0],
        DNS: [8, 8, 8, 8],
      };
      const wifi = ref(wifiInfo);
      const showConsoleLog = ref(false);
      const showSentLog = ref(false);
      const showReceivedLog = ref(true);
      const sendWiFiSetting = async function () {
        const divError = document.getElementById("divWiFiInfoError");
        divError.textContent = "";
        if (!wifiInfoValidation()) {
          divError.textContent = "invalid input...";
          return;
        }
        const formattedWifiData = {
          wifi_ssid: wifi.value.SSID,
          wifi_password: wifi.value.password,
          use_static_ip: wifi.value.useStaticIP,
          static_ip: wifi.value.staticIP.join("."),
          subnet_mask: wifi.value.subnetMask.join("."),
          gateway: wifi.value.gateway.join("."),
          dns_server: wifi.value.DNS.join("."),
        };
        console.log(JSON.stringify(formattedWifiData));
        nowSendingToSerial.value = true;
        await globalThis.webSerialManager.sendCommands([
          "import wifisetting",
          "wifisetting.save_wifi_config('" + JSON.stringify(formattedWifiData) + "')",
          "wifisetting.apply_wifi_config()",
        ]);
        nowSendingToSerial.value = false;
      };
      const wifiInfoValidation = function () {
        return wifi.value.SSID && wifi.value.password;
      };
      const doScanSSID = async function () {
        nowSendingToSerial.value = true;
        await globalThis.webSerialManager.sendCommands([
          "import wifisetting",
          "wifisetting.scanSSID()",
          "wifisetting.get_time_from_NTP()",
        ]);
        nowSendingToSerial.value = false;
      };
      const nowSendingToSerial = ref(false);
      return {
        showConsoleLog,
        ConsoleLog,
        showSentLog,
        SentLog,
        showReceivedLog,
        ReceivedLog,
        wasConnected,
        clickStart,
        clickClose,
        clickTest,
        wifi,
        doScanSSID,
        sendWiFiSetting,
        wifiInfoValidation,
        nowSendingToSerial,
      };
    },
  });
  app.mount("#formWebSerial");
</script>

ここでは、sendWiFiSettingという関数の中で以下の処理を実装しています

  • REPL経由でRaspberry Pi Pico WのMicroPythonに先ほど実装したwifisettingをimport
  • 画面から入力したWiFi接続文字列をJSONに編集
  • wifisettingの関数save_wifi_configを実行し、その引数として先ほどのJSONを引き渡し

これで、連載の当初に掲げた 「Raspberry Pi Pico WのWiFi接続情報をWebブラウザから設定する」 という機能が実現できました。PC側はすべてHTMLとJavaScriptで構成していますので、(第二回でも言及したように)WiFi接続情報以外の機器固有の情報(例:クラウドへの接続トークンなど)をサーバ側で生成し引き渡すことも可能です。

また、GUI制御についても、この画面で追加した機能以外はすべて前回のVueWebSerial.jsから再利用しています。これで、少なくともコードの再利用性という面では、SFC(Single File Component)形式に劣らない柔軟性をCDN版でも実現できたと自負しています。

最後に

全5回にわたり、Web Serial APIとREPLを活用してRaspberry Pi Pico WのWiFi設定を行う仕組みをご紹介してきました。

今回の実装は、IoTデバイスの実運用を視野に入れた構成であり、CDN版Vueによる柔軟なUI構築の一例にもなれば幸いです。

今後もRaspberry Pi Pico Wを活用した実装例を投稿していく予定ですので、ぜひフォロー・ご意見をお寄せいただけると嬉しいです!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?