Raspberry PiのWiFiをBLEでプロビジョニングする (balenaOS編)

# はじめに

IoTデバイスを配布する場合、WiFiのプロビジョニングが課題になります。

ESP32の場合 [Wi-Fi Provisioning](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/provisioning/wifi_provisioning.html) が提供されています。

解説ページ：
https://randomnerdtutorials.com/esp32-wi-fi-provisioning-ble-arduino/

Raspberry Piは [WiFi Connect](https://github.com/balena-os/wifi-connect) もありますが、ちょっと安定しないこともあり、ESP32と同様の方法を探していたところ [Rpi-SetWiFi-viaBluetooth](https://github.com/nksan/Rpi-SetWiFi-viaBluetooth) を見つけたので試してみました。

# Rpi-SetWiFi-viaBluetooth について

Rpi-SetWiFi-viaBluetoothはMITライセンスのオープンソースプロジェクトです。

ソースコードはこちら：
https://github.com/nksan/Rpi-SetWiFi-viaBluetooth

スマホアプリがあり、BLEを使ってRaspberry PiのWiFi(SSID/pass)を設定することができます。

ドキュメントはこちら：

https://normfrenette.com/Set-wifi-via-bluetooth/BTBerryWifi-Overview/

# balenaOSで使ってみる

balenaOS環境で使うためにいくつかのファイルを設定しました

```
root/
 ├ docker-compose.yml
 └ btwifiset/
    ├ Dockerfile.template
    └ entry.sh
```

```yaml:Dockerfile.template
ARG BALENA_ARCH=%%BALENA_ARCH%%

FROM balenalib/%%BALENA_MACHINE_NAME%%-debian-python:3.9-run
WORKDIR /usr/src

# DBUS is required for bluetooth-agent
ENV DBUS_SYSTEM_BUS_ADDRESS=unix:path=/host/run/dbus/system_bus_socket

# libraries
RUN install_packages wireless-tools network-manager bluez bluetooth libbluetooth-dev libudev-dev python3-gi libdbus-glib-1-dev libpython3.9-dev python3-dbus python3-cryptography

RUN python3 -m pip install dbus-python cryptography

RUN curl -O https://raw.githubusercontent.com/nksan/Rpi-SetWiFi-viaBluetooth/main/btwifiset.py 
RUN curl -O https://raw.githubusercontent.com/nksan/Rpi-SetWiFi-viaBluetooth/main/btpassword.py
RUN curl -O https://raw.githubusercontent.com/nksan/Rpi-SetWiFi-viaBluetooth/main/passwordREADME.txt

# set password
RUN echo password > crypto

ENV UDEV on

COPY . .

ENTRYPOINT [ "/bin/bash", "/usr/src/entry.sh" ]
CMD [ "python3", "/usr/src/btwifiset.py" ]
```

```sh:entry.sh
#!/bin/bash
set -e

# Run balena base image entrypoint script
/usr/bin/entry.sh echo "Running balena base image entrypoint..."

function reset_hci_interface () {
  local INTERFACE=$1

  echo "Resetting $i"
  btmgmt --index $i discov off > /dev/null
  btmgmt --index $i pairable off > /dev/null
  btmgmt --index $i connectable off > /dev/null
}

# Bluetooth block environment variables and defaults
DEVICE_NAME=${BLUETOOTH_DEVICE_NAME:-$(printf "balenaOS %s"$(echo "$BALENA_DEVICE_UUID" | cut -c -4))}
HCI_INTERFACE=${BLUETOOTH_HCI_INTERFACE:-"hci0"}
PAIRING_MODE=${BLUETOOTH_PAIRING_MODE:-"SSP"}
PIN_CODE=${BLUETOOTH_PIN_CODE:-"0000"}

echo "--- Bluetooth ---"
echo "Starting bluetooth service with settings:"
echo "- Device name: "$DEVICE_NAME
echo "- HCI interface: "$HCI_INTERFACE
echo "- Pairing mode: "$PAIRING_MODE
echo "- PIN code: "$PIN_CODE

# Get available interfaces
HCI_INTERFACES=$(btmgmt info | awk 'BEGIN { ORS=" " }; /^ *hci/ {gsub(":", ""); print $1}')
FS=' ' read -r -a HCI_INTERFACES <<< "$HCI_INTERFACES"
echo "Available HCI interfaces: "${HCI_INTERFACES[@]}

# Bail out if provided HCI interface is invalid
if [[ ! "${HCI_INTERFACES[@]}" =~ "$HCI_INTERFACE" ]]; then
  echo "Exiting... selected HCI interface is invalid: $HCI_INTERFACE"
  exit 0
fi

# Reset all interfaces. This helps shut off interfaces from previous runs.
for i in "${HCI_INTERFACES[@]}"
do
  reset_hci_interface "$i"
done

# Configure selected interface
echo "Configuring selected interface: $HCI_INTERFACE"
# Set device name
btmgmt --index $HCI_INTERFACE name "$DEVICE_NAME"

# Ensure bluetooth is ready to connect
btmgmt --index $HCI_INTERFACE connectable on
btmgmt --index $HCI_INTERFACE pairable on
btmgmt --index $HCI_INTERFACE discov on

# Set bluetooth pairing mode:
# - SSP (default): Secure Simple Pairing, no PIN code required
# - LEGACY: disable SSP mode, PIN code required
if [[ $PAIRING_MODE == "LEGACY" ]]; then
  AGENT_CAPABILITY="KeyboardDisplay"
  btmgmt --index $HCI_INTERFACE ssp off
  echo "Pairing mode set to 'Legacy Pairing Mode (LPM)'. PIN code is required."
else 
  AGENT_CAPABILITY="NoInputNoOutput"
  btmgmt --index $HCI_INTERFACE ssp on
  echo "Pairing mode set to 'Secure Simple Pairing Mode (SSPM)'. PIN code is NOT required."
fi

# If command starts with an option, prepend btwifiset.py to it
if [[ "${1#-}" != "$1" ]]; then
  set -- btwifiset.py "$@"
fi

exec "$@"
```

```yaml:docker-compose.yml
  btwifiset:
    build: ./btwifiset
    labels:
      io.balena.features.balena-socket: '1'
      io.balena.features.kernel-modules: '1'
      io.balena.features.firmware: '1'
      io.balena.features.dbus: '1'
      io.balena.features.supervisor-api: '1'
    cap_add:
      - NET_ADMIN
    environment:
      DBUS_SYSTEM_BUS_ADDRESS: "unix:path=/host/run/dbus/system_bus_socket"
    privileged: true
    network_mode: host
```

# iPhoneアプリ

App Storeから [BTBerryWifi](https://apps.apple.com/us/app/btberrywifi/id1596978011) アプリをインストールします。
Androidはテスト中とのこと。

![](https://normfrenette.com/static/8cc0a52f29ae58089f2211e015177ad7/a016c/selectpi2.png)

![](https://normfrenette.com/static/79c3e21ea3e3659022f12fc87d75a7cc/a016c/otherlistnksan.png)

簡単にWiFi設定できるようになりました。