LoginSignup
4
はじめての記事投稿

【Raspberry Pi Pico W】BluetoothでAndroidに接続してLチカ

Last updated at Posted at 2023-06-28

はじめまして、今回が初の投稿となります。
至らない点などありましたら、ご教授お願い致します。

参考文献

今回は以下のページ様にお世話になりました。

画像付きで詳しく書かれている為、
参考元の方がより分かりやすいかもしれません。

0.前提条件

まず、Thonnyを使ってMicroPythonで、
PicoWの開発&実行を行える環境がある事が前提です。

Thonnyのインストールがまだの方は、
他の方の記事を参考に先に設定を済ませてください。

※あまり古くなければ、特にバージョンは関係ないかもしれませんが、
 今回はThonny4.1.1で検証を行っています。

1.最新のMicroPythonファームウェアをPicoWにインストール

現在ThonnyからPicoWにインストールできるMicroPythonのファームウェアは
PicoWのBluetoothに対応していない為か、
これからご紹介するプログラムが上手く動作しませんでした。
ですので、対応するファームウェアをPicoWの公式サイトから
事前にダウンロード&インストールしておく必要があります。

👉Raspberry Pi Documentation から
最新のPicoW用MicroPythonファームウェアをダウンロードしてください。

※検証時のファームウェアは「micropython-firmware-pico-w-130623.uf2」でした。

インストールは、

  • 1)PicoWをBOOTSELボタンを押しながらUSBでPCに接続。
  • 2)Pico W の本体にUF2ファイルを配置。

すれば良いです。

2.PythonのプログラムをPicoWに保存

以下、3つのpythonファイルを PicoW に保存します。

それぞれ、Thonnyの画面に貼り付けて、
PicoWの内部に指定のファイル名で保存していきましょう。

main.py

from machine import Pin 
import bluetooth
from ble_simple_peripheral import BLESimplePeripheral

# Bluetooth Low Energy (BLE) オブジェクトを作成する。
ble = bluetooth.BLE()

# BLE オブジェクトを使用して BLESimplePeripheral クラスのインスタンスを作成。
sp = BLESimplePeripheral(ble)

# オンボード LED の Pin オブジェクトを作成し、出力として設定。
led = Pin("LED", Pin.OUT)

# LED の状態を 0 (オフ) に初期化します。
led_state = 0

# 受信したデータを処理するコールバック関数
def on_rx(data):
    # Bluetoothで受信したデータをコンソールに表示。
    print("Data received: ", data)
    # 受信したデータが「toggle」かどうかを確認。
    if data == b'toggle\r\n':
        # LED の状態を切り替える。( 0 or 1 )
        global led_state
        led.value(not led_state)
        # LED の状態を更新する。
        led_state = 1 - led_state
        # 更新した結果をBluetoothで送信。
        if led_state == 1:
            sp.send("ON\n")
        else:
            sp.send("OFF\n")

# メインループ
while True:
    # BLE 接続が確立されているかどうかを確認。
    if sp.is_connected():
        # データ受信用のコールバック関数を設定。
        sp.on_write(on_rx)

ble_simple_peripheral.py

ble_simple_peripheral.pyの 掲載元は コチラ

import bluetooth
import random
import struct
import time
from ble_advertising import advertising_payload

from micropython import const

_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_WRITE = const(3)

_FLAG_READ = const(0x0002)
_FLAG_WRITE_NO_RESPONSE = const(0x0004)
_FLAG_WRITE = const(0x0008)
_FLAG_NOTIFY = const(0x0010)

_UART_UUID = bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
_UART_TX = (
    bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"),
    _FLAG_READ | _FLAG_NOTIFY,
)
_UART_RX = (
    bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"),
    _FLAG_WRITE | _FLAG_WRITE_NO_RESPONSE,
)
_UART_SERVICE = (
    _UART_UUID,
    (_UART_TX, _UART_RX),
)

class BLESimplePeripheral:
    def __init__(self, ble, name="mpy-uart"):
        self._ble = ble
        self._ble.active(True)
        self._ble.irq(self._irq)
        ((self._handle_tx, self._handle_rx),) = self._ble.gatts_register_services((_UART_SERVICE,))
        self._connections = set()
        self._write_callback = None
        self._payload = advertising_payload(name=name, services=[_UART_UUID])
        self._advertise()

    def _irq(self, event, data):
        # 通知を送信できるように接続を追跡。
        if event == _IRQ_CENTRAL_CONNECT:
            conn_handle, _, _ = data
            print("New connection", conn_handle)
            self._connections.add(conn_handle)
        elif event == _IRQ_CENTRAL_DISCONNECT:
            conn_handle, _, _ = data
            print("Disconnected", conn_handle)
            self._connections.remove(conn_handle)
            # 新しい接続を許可するために、アドバタイズを再度開始。
            self._advertise()
        elif event == _IRQ_GATTS_WRITE:
            conn_handle, value_handle = data
            value = self._ble.gatts_read(value_handle)
            if value_handle == self._handle_rx and self._write_callback:
                self._write_callback(value)

    def send(self, data):
        for conn_handle in self._connections:
            self._ble.gatts_notify(conn_handle, self._handle_tx, data)

    def is_connected(self):
        return len(self._connections) > 0

    def _advertise(self, interval_us=500000):
        print("Starting advertising")
        self._ble.gap_advertise(interval_us, adv_data=self._payload)

    def on_write(self, callback):
        self._write_callback = callback

ble_advertising.py

ble_advertising.pyの 掲載元は コチラ

ble_advertising.py
from micropython import const
import struct
import bluetooth

# advertising payloadは、次の形式の繰り返しパケットです。
#    1バイトのデータ長(N+1)
#    1 バイト型 (以下の定数を参照)
#    N バイトのタイプ固有のデータ

_ADV_TYPE_FLAGS = const(0x01)
_ADV_TYPE_NAME = const(0x09)
_ADV_TYPE_UUID16_COMPLETE = const(0x3)
_ADV_TYPE_UUID32_COMPLETE = const(0x5)
_ADV_TYPE_UUID128_COMPLETE = const(0x7)
_ADV_TYPE_UUID16_MORE = const(0x2)
_ADV_TYPE_UUID32_MORE = const(0x4)
_ADV_TYPE_UUID128_MORE = const(0x6)
_ADV_TYPE_APPEARANCE = const(0x19)


# gap_advertise(adv_data=...) に渡すpayloadを生成
def advertising_payload(limited_disc=False, br_edr=False, name=None, services=None, appearance=0):
    payload = bytearray()

    def _append(adv_type, value):
        nonlocal payload
        payload += struct.pack("BB", len(value) + 1, adv_type) + value

    _append(
        _ADV_TYPE_FLAGS,
        struct.pack("B", (0x01 if limited_disc else 0x02) + (0x18 if br_edr else 0x04)),
    )

    if name:
        _append(_ADV_TYPE_NAME, name)

    if services:
        for uuid in services:
            b = bytes(uuid)
            if len(b) == 2:
                _append(_ADV_TYPE_UUID16_COMPLETE, b)
            elif len(b) == 4:
                _append(_ADV_TYPE_UUID32_COMPLETE, b)
            elif len(b) == 16:
                _append(_ADV_TYPE_UUID128_COMPLETE, b)

    # org.bluetooth.characteristic.gap.appearance.xml を参照。
    if appearance:
        _append(_ADV_TYPE_APPEARANCE, struct.pack("<h", appearance))

    return payload


def decode_field(payload, adv_type):
    i = 0
    result = []
    while i + 1 < len(payload):
        if payload[i + 1] == adv_type:
            result.append(payload[i + 2 : i + payload[i] + 1])
        i += 1 + payload[i]
    return result


def decode_name(payload):
    n = decode_field(payload, _ADV_TYPE_NAME)
    return str(n[0], "utf-8") if n else ""


def decode_services(payload):
    services = []
    for u in decode_field(payload, _ADV_TYPE_UUID16_COMPLETE):
        services.append(bluetooth.UUID(struct.unpack("<h", u)[0]))
    for u in decode_field(payload, _ADV_TYPE_UUID32_COMPLETE):
        services.append(bluetooth.UUID(struct.unpack("<d", u)[0]))
    for u in decode_field(payload, _ADV_TYPE_UUID128_COMPLETE):
        services.append(bluetooth.UUID(u))
    return services

3.Thonnyで実行

ソースコードのmain.pyタブを選択して、
F5 キーを押すか、「実行」アイコンをクリックしてコードを実行すると、
Thonnyのシェルに「Starting advertising」と表示されます。

4.Android側の操作

GooglePlayより Serial Bluetooth Terminal をダウンロード&インストールして使用しました。

※通信相手として検証に使ったのはスマートフォンのAndroi10です。

  • 1)まずアプリを立ち上げて、メニューから[Devices]を選択します。
  • 2)[Bluetooth LE]タブを選択し、[SCAN]をタップします。これで、デバイスがmpy-uartとしてリストされているはずです。
  • 3)接続が確立されると、Android アプリに「Connected」
    というメッセージが表示されます。Thonnyのシェルにも「New Connection 64」というテキストが表示されます。
  • 4)Android アプリで、引用符なしで「toggle」と入力し、送信アイコンをタップすると、PicoWのLEDが点灯し、Android側は「ON」という文字列が表示されます。

IMG_3355.jpeg

以上です。様々なセンサーやモーターなどを
遠隔で操作するのに応用してみてください。

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
4