1
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?

AndroidスマホからRaspberry Piへ通知を転送し、GPSモジュールなしで位置情報も使う

1
Posted at

あまり知られていませんが、AndroidスマートフォンとRaspberry PiをBLEでつなぐと、スマートフォンに届いた通知をRaspberry Pi側で受け取れます。さらに、GPSモジュールを載せていなくても、スマートフォンの位置情報をGPS受信機の代わりに使えます。正確な時刻やGoogle Mapsのナビゲーション案内も、同じ接続で届きます。

これを可能にするのが、オープンソースのAndroidアプリGadgetbridgeです。専用のAndroidアプリを自作しなくても、省電力な1本のBLE接続でスマートフォンの情報をRaspberry Piへ渡せます。今回、これをPythonから扱うためのパッケージを作りました。

本記事では、gadgetbridge-rpi-linkで何ができるのか、Raspberry PiとAndroidを接続する手順、ホストアプリケーションへ組み込む方法、現時点の制約を紹介します。

TL;DR

gadgetbridge-rpi-linkを使うと、Raspberry Piなどの小型LinuxデバイスをBangle.js互換デバイスとしてGadgetbridgeへ接続できます。

1本のBLE接続で、次の機能を利用できます。

  • Androidに届いた通知の追加、更新、削除
  • AndroidのGPS位置情報
  • スマートフォンの時刻
  • Google Mapsのターンバイターンナビゲーション
  • スマートフォン経由の小さなHTTPリクエスト
  • Raspberry PiからAndroidインテントの実行(音声アシスタントの起動、Termuxのコマンド実行など)

Raspberry Pi側では、受信内容を型付きのPythonイベントとして扱えます。通知を画面へ描画する処理や、位置情報を地図へ渡す処理は、利用するアプリケーション側で実装します。

本パッケージはPython 3.13以降を対象としています。BLE UARTサービスをホストする場合は、BlueZを利用できるLinux環境も必要です。ライセンスはMITです。

Gadgetbridgeとは

Gadgetbridgeは、ベンダーのクラウドアカウントを使わずにスマートウォッチやフィットネストラッカーをAndroidへ接続できるオープンソースアプリです。

GadgetbridgeにはBangle.js向けのBLE UARTプロトコルがあります。このプロトコルでは、Androidの通知や位置情報などをBLE経由でデバイスへ送信できます。

今回作成したgadgetbridge-rpi-linkは、Raspberry Pi上でBangle.js向けBLE UARTサービスをホストし、Gadgetbridgeから届くメッセージをPythonで扱うためのライブラリです。

構成を大まかに表すと、次のようになります。

Android
  └─ Gadgetbridge
       └─ Bangle.js UART over BLE
            └─ gadgetbridge-rpi-link
                 └─ 自作アプリケーションのUI、地図、ログなど

Gadgetbridgeとのプロトコルについては、EspruinoのGadgetbridgeドキュメントも参照してください。

できること

Androidの通知をRaspberry Piへ転送する

GadgetbridgeにAndroidの通知アクセスを許可すると、通知の追加、更新、削除がRaspberry Piへ送られます。

AndroidからRaspberry Piのディスプレイへ転送された通知

受信内容は、通知の追加、更新、削除を区別できるPythonイベントへ変換されます。追加と更新のイベントには、通知元、タイトル、本文、送信者などが含まれます。自作デバイスのディスプレイへ表示したり、必要な通知だけを選別したりできます。

ただし、日本語を含むCJKのマルチバイト文字には、現時点でUTF-8対応のカスタムGadgetbridgeビルドが必要です。詳しくは後述します。

AndroidをGPS受信機として使う

Gadgetbridgeに登録したデバイスの設定でAndroid の GPS データを使用するを有効にすると、Androidの位置情報がPythonイベントとして届きます。

スマートフォンのGPS位置情報をRaspberry Piへ転送

イベントからは、緯度、経度、高度、速度、方位、HDOP、測位モード、衛星数、時刻を取得できます。すべてのフィールドが常に届くとは限らないため、値はNoneになり得るものとして扱います。

スマートフォンをGPS受信機として使えば、Raspberry Pi側のGPSモジュールを省略できます。自作サイクルコンピューターや、スマートフォンと一緒に持ち歩く表示端末に向いた構成です。

RTCのないRaspberry Piへ時刻を渡す

RTCを搭載しないRaspberry Piでは、電源投入直後の時刻が正しくないことがあります。Gadgetbridgeから届いた時刻はPythonイベントへ変換されるため、ホストアプリケーションからシステムへ反映できます。

Gadgetbridge接続後にスマートフォンの時刻をRaspberry Piへ反映

ただし、これは起動直後から使えるRTCの完全な代替ではありません。BLE接続とGadgetbridgeの同期が完了するまで時刻は届かないため、それ以前に記録したタイムスタンプは正しくない可能性があります。

Google Mapsのナビゲーションを読み取る

Google Mapsのナビゲーション通知は、GadgetbridgeによってRaspberry Piへ転送されます。Raspberry Pi側では、曲がる方向、案内文、残り距離などをPythonイベントから取得できます。

Google MapsのナビゲーションをRaspberry Piへ転送

独自に経路探索APIを実装しなくても、Google Mapsが出すターンバイターンの案内を自作ディスプレイへ表示できます。

スマートフォン経由でHTTPリクエストを行う

Raspberry PiからGadgetbridgeへHTTPリクエストを送り、Androidを小さなネットワークブリッジとして利用できます。

Raspberry Piからスマートフォン経由でテキストやJSONを取得

GETだけでなく、method="POST"とリクエストボディを指定した送信にも対応しています。一方で、現在のGadgetbridgeの実装はテキスト指向です。テキストやJSONなどの小さなデータを対象とし、画像、ZIP、FITファイルなどの汎用的なバイナリ転送には使用しないでください。

Raspberry PiからAndroidインテントを実行する

Gadgetbridgeでインテントを許可するを有効にすると、Raspberry PiからAndroidインテントを送れます。スマートフォンの画面に触れずに、デバイス側のボタンやコマンドからAndroid側の操作を起動できる機能です。

たとえば、スマートフォンに設定されている音声アシスタント(Googleアシスタントなど)を起動できます。Bluetoothヘッドセットをスマートフォンとペアリングしておけば、スマートフォンを操作することなく、Raspberry Piからインテントを送るだけで、そのまま音声で会話できます。

session.send_intent(
    "android.intent.action.VOICE_COMMAND",
    flags=["FLAG_ACTIVITY_NEW_TASK"],
)

インテントにはtargetpackageclass_nameextraも指定できます。これを使うと、TermuxRUN_COMMANDサービスを呼び出して、スマートフォンへ置いた任意のスクリプトをRaspberry Piから実行できます。

session.send_intent(
    "com.termux.RUN_COMMAND",
    target="service",
    package="com.termux",
    class_name="com.termux.app.RunCommandService",
    extra={
        "com.termux.RUN_COMMAND_PATH": "/data/data/com.termux/files/home/run-task.sh",
    },
)

Termux側では、termux.propertiesallow-external-apps = trueを設定し、Androidのアプリ設定でGadgetbridgeへTermuxのコマンド実行パーミッションを付与しておきます。なお、Gadgetbridgeのインテントextraは実質的に文字列のみで、booleanなどを正しい型では渡せません。

筆者は、自作サイクルコンピューターのボタンからこれらを組み合わせて、走行中にスマートフォンを取り出さずに音声アシスタントとやり取りする実験をしています。

なぜBLEを使うのか

通知、GPS、時刻、ナビゲーション、軽量なHTTPを、すべて同じBLE接続でやり取りできることが本構成のポイントです。

接続方式 スマートフォンのバッテリー消費 向いている用途
BLE 最小 通知、GPS、時刻、軽量なテキストやJSON
Bluetoothテザリング Raspberry Piに通常のIP接続が必要な場合
Wi-Fiテザリング 画像などを含む広帯域の通信が必要な場合

BLEは帯域が小さいため万能ではありません。しかし、サイクルコンピューターやウェアラブルディスプレイのように、バッテリーで長時間動かしながら少量のデータを継続的に受け取る用途には適しています。

Raspberry PiとAndroidを接続する

必要なもの

  • Bluetoothを利用できるRaspberry PiなどのLinuxデバイス
  • BlueZを利用できるLinux環境
  • Python 3.13以降
  • Androidスマートフォン
  • Android版Gadgetbridge

HTTP機能と日本語通知には追加条件があります。まずは通常版Gadgetbridgeで、接続とGPSなどの基本機能から確認するのがおすすめです。

パッケージをインストールする

仮想環境を作成して、PyPIからインストールします。

$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install gadgetbridge-rpi-link

uvを使用する場合、依存するbluez-peripheralが現在アルファ版として公開されているため、プレリリースを許可します。

$ uv pip install --prerelease=allow gadgetbridge-rpi-link

PyPIの配布名はgadgetbridge-rpi-link、Pythonのimport名はgadgetbridge_rpi_linkです。

BLEホストを起動する

手動確認用のBLEホストを起動します。

$ gadgetbridge-rpi-bluez --product gadgetbridge-rpi-link

このコマンドはBLE UARTサービスをアドバタイズし、Gadgetbridgeから届いたイベントをログへ出力します。

常用アプリケーションではなく、最初の接続確認と機能ごとのスモークテストに使う小さなプローブです。

Gadgetbridgeへ登録する

AndroidへGadgetbridgeをインストールして、次の手順でRaspberry Piを登録します。

  1. Gadgetbridgeのデバイス検出画面を開きます。
  2. 未サポートのデバイスも表示するを有効にします。
  3. デバイスをスキャンします。
  4. 表示されたRaspberry Piを長押しします。
  5. テスト デバイスを追加するを選びます。
  6. デバイス種別としてBangle.jsを選択します。

登録できると、Raspberry Pi側のコンソールへGadgetbridgeから届いたイベントが表示されます。

機能を試す

手動確認用コマンドを起動すると、次のキーを入力できます。

キー 動作
t Gadgetbridgeへ任意のテキストメッセージを送る
g AndroidのGPSをONにするよう要求する
G Androidの音声コマンドインテントを送る
h 公式サンプルURLからテキストを取得して保存する
q 終了する

tを入力した後は、Gadgetbridgeへ送るメッセージ本体を1行で入力します。

{t:"info", msg:"OK"}
{"t":"status","bat":"23"}
{t:"http", url:"https://pur3.co.uk/hello.txt"}

GPSを確認する

登録したBangle.jsデバイスの設定で、次の項目を確認します。

  • Androidの位置情報権限をGadgetbridgeへ付与する
  • Android の GPS データを使用するを有効にする
  • 用途に合うGPS データの更新間隔を設定する

BLEホストのコンソールでgを入力すると、AndroidへGPS ONを要求します。位置情報を受信すると、緯度、経度、速度などがログへ表示されます。

--auto-gpsオプションは、スマートフォン側のGPS状態を受信した後、自動的にGPSをONにするよう要求したい場合にのみ使用してください。

通知を確認する

Androidの設定でGadgetbridgeに通知アクセスを許可します。特定のアプリだけを転送したい場合は、Gadgetbridgeの通知フィルターも確認します。

Androidへ通知が届くと、Raspberry Pi側には通知のタイトルと本文などがイベントとして届きます。

時刻を確認する

リポジトリには、スマートフォンから受信した時刻をシステムへ反映するサンプルがあります。サンプル自身がBLEホストとして動作するため、手動確認用コマンドは停止しておきます。

ソースをcloneした環境で、最初はdry-runとして実行します。

$ PYTHONPATH=src python3 examples/set_system_time.py

受信したUTC時刻と、実際に実行するコマンドを確認できます。システム時刻を変更する権限を適切に設定した後、--applyを付けるとsudo -n date -u --set ...を実行します。

$ PYTHONPATH=src python3 examples/set_system_time.py --apply

時刻変更はシステム全体へ影響します。実機でいきなり--applyを付けず、受信時刻、タイムゾーン、権限設定をdry-runで確認してください。

HTTPを確認する

HTTPを利用するには、次のどちらかの構成が必要です。

  1. Bangle.jsフレーバーのGadgetbridgeを使う
  2. 通常版Gadgetbridgeへ公式のInternet Helperを設定する

Bangle.jsフレーバーは、Espruinoの案内から入手するか、Gadgetbridgeをソースからビルドします。Android Studioでビルドする場合は、Build VariantをmainlineDebugからbanglejsDebugなどのbanglejsで始まるものへ変更します。

登録したデバイスの設定でインターネット接続を許可するも有効にしてください。

準備ができたら、BLEホストのコンソールでhを入力します。Espruinoが公開しているテキストサンプルを取得し、ローカルのtmpディレクトリへ保存します。

ナビゲーションを確認する

Gadgetbridgeへ通知アクセスを許可し、Google Mapsでナビゲーションを開始します。

最近のAndroidでは、Google Mapsのライブ通知カテゴリによって、案内の詳細をGadgetbridgeから読み取れないことがあります。曲がる方向や距離が空の場合は、AndroidのGoogle Maps通知設定でLive Updatesまたはライブ情報などの通知カテゴリを無効にしてください。

Androidインテントを確認する

登録したデバイスの設定でインテントを許可するを有効にします。バックグラウンドでの実行には、Androidの他のアプリの上に重ねて表示する権限が必要になる場合があります。

BLEホストのコンソールでGを入力すると、音声コマンドインテントを送信します。

自作アプリケーションへ組み込む

受信データを型付きイベントとして扱う

すでに別のBLE UARTトランスポートがある場合は、受信したバイト列をGadgetbridgeProtocol.feed_rx()へ渡します。

from gadgetbridge_rpi_link import (
    GadgetbridgeProtocol,
    GpsFixEvent,
    NotificationAddEvent,
)

protocol = GadgetbridgeProtocol()

events = protocol.feed_rx(
    b'\x10GB({"t":"notify","title":"Chat","body":"Hello"})\n'
)

for event in events:
    if isinstance(event, NotificationAddEvent):
        print(event.title, event.body)
    elif isinstance(event, GpsFixEvent):
        print(event.lat, event.lon)

この処理では、BLEで分割されて届いたデータを元のメッセージへ戻し、内容に応じたPythonイベントへ変換します。解釈できなかったメッセージや未知のメッセージも、捨てずにイベントとして受け取れます。

BlueZのBLE UARTサーバーを直接使う

Linux上でBLEサービスごとホストする場合は、BluezGadgetbridgeUartServerを使えます。

import asyncio

from gadgetbridge_rpi_link import (
    GpsFixEvent,
    NotificationAddEvent,
)
from gadgetbridge_rpi_link.bluez import BluezGadgetbridgeUartServer


def handle_event(event):
    if isinstance(event, NotificationAddEvent):
        print(f"{event.title}: {event.body}")
    elif isinstance(event, GpsFixEvent):
        print(f"{event.lat}, {event.lon}")


async def main():
    server = BluezGadgetbridgeUartServer(
        product="my-rpi-device",
        on_event=handle_event,
    )
    await server.start()
    try:
        await asyncio.Event().wait()
    finally:
        await server.stop()


asyncio.run(main())

イベントを受け取る関数では、ディスプレイの更新、地図への位置情報の反映、ログへの保存など、デバイス固有の処理を行います。

Gadgetbridgeへメッセージを送る

既存のBLEトランスポートへ送信する場合は、辞書をGadgetbridgeProtocol.encode_tx()へ渡します。ライブラリがGadgetbridgeへ送れる形式に変換し、BLEで送れる大きさに分割します。

from gadgetbridge_rpi_link import DEFAULT_HTTP_TEXT_URL, GadgetbridgeProtocol

protocol = GadgetbridgeProtocol()

for data in protocol.encode_tx({"t": "info", "msg": "OK"}):
    # Send `data` through the BLE UART TX characteristic.
    ...

for data in protocol.encode_tx({"t": "http", "url": DEFAULT_HTTP_TEXT_URL}):
    # Send `data` through the BLE UART TX characteristic.
    ...

リクエストとレスポンスを対応付ける必要があるHTTPでは、GadgetbridgeSessionを使うと非同期に待機できます。受信側では、受け取ったイベントを同じセッションのhandle_event()へ渡します。

from gadgetbridge_rpi_link import GadgetbridgeSession


async def sender(message: str) -> None:
    # Send `message` through a BLE UART transport.
    ...


session = GadgetbridgeSession(sender=sender)


def handle_event(event) -> None:
    session.handle_event(event)


async def fetch_data() -> None:
    response = await session.request_http(
        "https://example.com/data.json",
        timeout=10,
    )
    print(response.get("resp"))

handle_event()は、BLE受信側のイベントコールバックから呼び出します。生の受信バイト列をセッションへ直接渡せる構成では、session.feed_rx()を使用することもできます。

現時点の制約

日本語通知にはGadgetbridgeのUTF-8対応が必要

GadgetbridgeのBangle.js向けBLE UARTは、現在、文字列の送受信にLatin-1を使用しています。

日本語などのマルチバイト文字を送受信するには、送受信の文字コードをUTF-8へ変更し、カスタムGadgetbridgeをビルドする必要があります。

ASCIIだけで接続確認を済ませてから、UTF-8対応ビルドへ進むと問題を切り分けやすくなります。

HTTPはテキストと小さなデータ向け

付属のHTTPファイル保存機能にはbinaryオプションがありますが、これは受信済みデータをローカルへどう書き込むかを指定するものです。AndroidからRaspberry Piまでの通信経路を、汎用的なバイナリ転送へ変えるものではありません。

現在のGadgetbridgeのHTTP実装では、大きなバイナリをテキスト経路で受け取ると、Android側ですでに置換文字が入ることがあります。その状態から元のバイト列を復元することはできません。

天気情報や小さな設定ファイルなどのテキスト/JSONには使えますが、地図画像やアップデート用アーカイブの取得には、BluetoothテザリングやWi-Fiなどの通常のIP接続を使う方が適切です。

UIやデータ保存は含まない

本ライブラリは、BLE UARTで受信したデータをPythonイベントに変換し、送信データの整形やHTTPリクエストの対応付けを行います。

次の処理はホストアプリケーション側で実装します。

  • 通知やナビゲーションの描画
  • GPSデータの利用や保存
  • システム時刻の変更
  • 受信したHTTPデータの解釈
  • アプリケーション固有の再接続や状態管理

この境界により、GUIフレームワークやディスプレイ、ログ形式を固定せず、既存の自作デバイスへ組み込みやすくしています。

まとめ

gadgetbridge-rpi-linkを使うと、Androidスマートフォンが持つ情報をRaspberry Piの入力源として利用できます。

通知、GPS、時刻、ナビゲーション、軽量なHTTPを1本のBLE接続へまとめられるため、Wi-Fiテザリングを常時動かしたくないバッテリー駆動デバイスに向いています。特に、自作サイクルコンピューター、ウェアラブルディスプレイ、屋外用の小型情報端末などで使いやすい構成です。

一方、BLEは広帯域ネットワークの代替ではなく、日本語通知とHTTPにはGadgetbridge側の制約もあります。まずは付属のBLEホストで通知やGPSを確認し、必要なイベントを自作アプリケーションへ接続してみてください。

ソースコード、詳しいAPI、最新の制約はGitHubで公開しています。

1
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
1
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?