PiPicoWでAndroidとBluetooth接続

### RaspberryPiPicoWの内蔵温度センサの値を、BLE（Bluetooth Low Energy）で接続してスマホに表示

目的：RaspberryPiPicoWの温度センサとBLE機能を確認する
用意するもの：RaspberryPiPicoW、書き込み用PC、スマホ

#### １）BLE機能が使える最新ファームウェア.uf2をDL

《 microPythonのサイト 》
　　ファイル名の例：v1.25.0 (2025-04-15) .uf2 / [Release notes] (latest)
  　　☞　今回はこのファイルを使用

  https://micropython.org/download/RPI_PICO_W/

《 ラズパイ公式サイト 》
　　ファイル名の例：RPI_PICO_W-20250415-v1.25.0.uf2

https://www.raspberrypi.com/documentation/microcontrollers/micropython.html


#### ２）Bootボタンを押しながらPiPicoWを起動。

#### ３）PiPicoWのフォルダウィンドウに.uf2をドラッグ＆ドロップ

ファイルのドロップが完了したら、PicoWのフォルダウィンドウは自動的に閉じる
    
#### ４）以下の3ファイルを、ThonnyでPicoW内に保存
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4018803/60b3201e-d7a0-4e92-9ba0-69bbce049c67.png)
それぞれのファイルの用途
    main.py　…　実行ファイル
    ble_advertising.py　…　アドバタイズ用（BLE情報を定期的に発信）
    ble_temp_sensor.py　…　温度を取得して送信

```python
# ファイル名：main.py
import time
from machine import ADC
from ble_temp_sensor import BLETemperature
import bluetooth

# 温度センサー初期化
sensor_temp = ADC(4)
conversion_factor = 3.3 / 65535

def read_temp():
    reading = sensor_temp.read_u16() * conversion_factor
    temp_c = 27 - (reading - 0.706)/0.001721
    return round(temp_c, 2)

# BLE初期化
ble = bluetooth.BLE()
temp_service = BLETemperature(ble)
print("BLE initialized and advertising...")  # ← 追加

while True:
    temperature = read_temp()
    print("Temp:", temperature)
    temp_service.send_temperature(temperature)
    time.sleep(2)
```

```python
# ファイル名：ble_temp_sensor.py
import struct
import bluetooth
from micropython import const
from ble_advertising import advertising_payload

# 短いUUIDに変更（標準定義）
_TEMP_SERVICE_UUID = bluetooth.UUID(0x1809)   # Health Thermometer （BLE公式規格）
_TEMP_CHAR_UUID = bluetooth.UUID(0x2A1C)      # Temperature Measurement　（公式）

# flags
_FLAG_READ = const(0x0002)
_FLAG_NOTIFY = const(0x0010)

_IRQ_CENTRAL_CONNECT = const(1) #bluetoothライブラリ使わなくてよいように追加
_IRQ_CENTRAL_DISCONNECT = const(2) #同上


class BLETemperature:
    def __init__(self, ble, name="PicoTemp"):
        self._ble = ble
        self._ble.active(True)
        self._ble.irq(self._irq)
        self._connections = set()

        # サービス登録
        ((self._temp_handle,),) = self._ble.gatts_register_services((
            (_TEMP_SERVICE_UUID, (
                (_TEMP_CHAR_UUID, _FLAG_READ | _FLAG_NOTIFY),
            )),
        ))

        # アドバタイズ用データ
        self._payload = advertising_payload(name=name, services=[_TEMP_SERVICE_UUID])
        self._advertise()

    def _irq(self, event, data):
        if event == _IRQ_CENTRAL_CONNECT: #変更
            conn_handle, _, _ = data
            print("Connected:", conn_handle)
            self._connections.add(conn_handle)
        elif event == _IRQ_CENTRAL_DISCONNECT: #変更
            conn_handle, _, _ = data
            print("Disconnected:", conn_handle)
            self._connections.discard(conn_handle)
            self._advertise()

    def _advertise(self):
        # アドバタイズ間隔を100msに（安定する値）
        self._ble.gap_advertise(100_000, adv_data=self._payload)

    def send_temperature(self, temp_c):
        temp_str = "{:.1f}℃".format(temp_c)
        # 元は、temp_bytes = struct.pack("<f", temp_c)  # floatをバイト列に変換だった
        temp_bytes = temp_str.encode('utf-8')
        for conn_handle in self._connections:
            self._ble.gatts_notify(conn_handle, self._temp_handle, temp_bytes)
```

```python
# ファイル名：ble_advertising.py
import struct
import bluetooth

_ADV_TYPE_FLAGS = const(0x01)
_ADV_TYPE_NAME = const(0x09)
_ADV_TYPE_UUID16_COMPLETE = const(0x03)
_ADV_TYPE_UUID32_COMPLETE = const(0x05)
_ADV_TYPE_UUID128_COMPLETE = const(0x07)

def advertising_payload(limited_disc=False, br_edr=False, name=None, services=None):
    payload = bytearray()

    flags = 0x02 if limited_disc else 0x06
    if br_edr:
        flags |= 0x18
    payload += struct.pack("BB", 2, _ADV_TYPE_FLAGS) + bytes([flags])

    if name:
        payload += struct.pack("BB", len(name) + 1, _ADV_TYPE_NAME) + name.encode()

    if services:
        for uuid in services:
            b = bytes(uuid)
            if len(b) == 2:
                payload += struct.pack("BB", len(b) + 1, _ADV_TYPE_UUID16_COMPLETE) + b
            elif len(b) == 4:
                payload += struct.pack("BB", len(b) + 1, _ADV_TYPE_UUID32_COMPLETE) + b
            elif len(b) == 16:
                payload += struct.pack("BB", len(b) + 1, _ADV_TYPE_UUID128_COMPLETE) + b

    return payload
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4018803/fda008b6-1757-47c7-bafa-aa23cbbe20da.png)
MicroPython公式のBLE温度サンプル

https://github.com/micropython/micropython/blob/master/examples/bluetooth/ble_temperature.py

#### ５）main.pyを実行
　　Thonnyのシェルに　print("Temp:", temperature)　の実行結果として温度が表示される

#### ６）スマホにLightBlueをインストール

https://play.google.com/store/apps/details?id=com.punchthrough.lightblueexplorer&hl=ja

nRFconnectでもValueの確認は可能
ただ、アプリ側でUTF-8にエンコード表示する設定が見当たらない。

https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp&hl=ja


#### ７）PicoWのAdvertisingをLightBlueで確認する

順に赤〇の部分をタップ
[Peripherals]　＞　PicoTemp　の　[Connect]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4018803/d7488c12-0dca-47b3-b8a3-ad2c66f80db4.png)

PicoTemp　が [connected]　であるのを確認して、Health Thermometerの項目の[Temperature Measurement]をタップ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4018803/d4b83735-b4f9-4626-bcc6-f0ef903c30d0.png)

Characteristic　内の [Subscribe] をタップ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4018803/d60cbad6-af1f-4ff3-988d-f73e802bea28.png)

すると、Read/Notified Values の下に、取得値が更新表示される
値がHEX表示のままの場合、赤〇部分が[HEX]となっていると思うので、タップして[UTF-8 String]に変更する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4018803/b69783f2-9f22-4c07-9ff9-841acbbd3928.png)