ラズベリーパイPico WでGPS機能を設けてみる


ラズベリーパイPico W(ラズベリーパイPico 2 Wも可:以後ラズピコ)でGPSの機能を設けてみます。

※GPS(Global Positioning System)はアメリカで運用している衛星測位システムを指すそうで、人工衛星から緯度経度を測位するのはGNSS(Global Navigation Satellite System:ジーエヌエスエス)と呼ぶそうです。

※以後、GPSの代わりにGNSSと表記します

Raspberry Pi Pico 2 W — スイッチサイエンス


ラズピコでGNSSを利用するには、




Waveshare社のPico-GPS-L76KのようなGNSSに対応したモジュールを利用するか、Googleが提供しているGeolocation APIを利用します。

※GNSSとGeolocation APIを効率的に組み合わせた測位システムをA-GNSS(Assisted-GNSS)と呼びます。

Waveshare Pico-GPS-L76K - スイッチサイエンス

Geolocation API の概要  |  Google for Developers


前者のGNSSは測位用の人工衛星を利用して、緯度経度を測位する仕組みになります。

GNSSは専用のアンテナがあればインターネットに接続しなくても測位できますが、屋根のような遮蔽物や雨等の干渉に弱いというデメリットがあります。


この内容を補完するような位置付けとして、Geolocation APIの緯度経度の推定があります。

Geolocation APIはGoogleが全国各地にあるWi-Fiのアクセスポイントの所有していて、そのアクセスポイントから自身のいる場所を推定します。


今回は後者のGeolocation APIの機能について見ていきます。




始めにGoogle Cloud consoleを開き、ライブラリからGeolocation APIを探し有効にします。

※詳しい操作方法はGeolocation API を設定する  |  Google for Developersをご覧ください。

※Geolocation APIは毎月一定以上利用すると課金する必要がありますので、使用前に金額についてご確認した上でご利用ください。

Geolocation API の使用量と請求額  |  Google for Developers


Geolocation APIを有効にしたら、次は認証情報のページを探し、APIキーを作成します。

※ Google Cloud consoleのUIの変更が多い為、この記事でAPIキーの作成に関しての詳細は触れません。


APIキーが取得出来たら、ラズピコの方でコードの作成を行います。

import network
import time
import ujson
import urequests

WIFI_SSID = {使用可能なWiFiのSSIDを入力します}
WIFI_PASSWORD = {上記のSSID用のパスワードを入力します}

# Google Geolocation APIキーを設定
GEOLOCATION_API_KEY = {Google Cloud Platformで取得したAPIキーを入力します}

# Wi-Fiに接続する為の関数
def connect_to_wifi(ssid, password):
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    
    # 既に接続されているか確認
    if wlan.isconnected():
        print("既に接続されています:", wlan.ifconfig()[0])
        return wlan

    print(f"Wi-Fi接続中: {ssid}...")
    wlan.connect(ssid, password)
    
    # 接続が確立するまで待機
    max_wait = 15
    while max_wait > 0:
        if wlan.status() < 0 or wlan.status() >= 3:
            break
        max_wait -= 1
        print('.', end='')
        time.sleep(1)

    if wlan.status() != 3:
        raise RuntimeError('ネットワーク接続に失敗しました')
    else:
        status = wlan.ifconfig()
        print('\n接続成功! IPアドレス:', status[0])
        return wlan

# Wi-Fiスキャンとデータ整形
def get_wifi_data(wlan):
    #周囲のアクセスポイントをスキャンし、APIリクエスト用の形式に整形
    print("周囲のWi-Fi APをスキャン中...")
    ap_list = wlan.scan()
    
    wifi_data = []
    
    # スキャン結果を反復処理
    for ssid, bssid, channel, rssi, authmode, hidden in ap_list:
        # BSSID (MACアドレス) を文字列に変換 ('C8:D7:19:...' 形式)
        mac_address = ':'.join(f'{b:02X}' for b in bssid)
        
        # 信号強度が極端に弱いAPは除外するなどのフィルタリングも可能
        if rssi > -90: 
            wifi_data.append({
                "macAddress": mac_address,
                "signalStrength": rssi,
                "channel": channel
            })
            # print(f" - AP検出: {mac_address}, RSSI: {rssi}")

    print(f"{len(wifi_data)}件のAP情報を収集しました。")
    return wifi_data

# Geolocation APIにリクエスト
def get_location_from_api(wifi_access_points):
    
    # Geolocation APIのエンドポイント
    API_URL = f"https://www.googleapis.com/geolocation/v1/geolocate?key={GEOLOCATION_API_KEY}"

    # リクエストペイロードの作成
    request_payload = {
        # Wi-Fi APデータを使用
        "wifiAccessPoints": wifi_access_points,
        # Wi-Fiデータが不十分な場合、IPアドレスも推定に使用することを許可
        "considerIp": "true" 
    }
    
    try:
        print("\nGeolocation APIにリクエストを送信中 (POST)...")
        
        # HTTPS POSTリクエストの実行
        response = urequests.post(
            API_URL, 
            data=ujson.dumps(request_payload), 
            headers={'Content-Type': 'application/json'}
        )
        
        # レスポンスの解析
        if response.status_code == 200:
            result = response.json()
            
            # 結果の抽出
            latitude = result['location']['lat']
            longitude = result['location']['lng']
            accuracy = result.get('accuracy', 'N/A') # 精度 (accuracy) がない場合もある
            
            print("--- 位置情報結果 ---")
            print(f"緯度 (Latitude): {latitude}")
            print(f"経度 (Longitude): {longitude}")
            print(f"推定精度 (Accuracy): {accuracy} メートル")
            print("------------------")
            
        else:
            print(f"APIエラー: ステータスコード {response.status_code}")
            print(f"レスポンステキスト: {response.text}")
            
        response.close() # メモリリークを防ぐため、Responseオブジェクトを必ず閉じる
        
    except Exception as e:
        print(f"HTTPリクエスト中にエラーが発生しました: {e}")
    finally:
        # `finally`ブロックで確実に閉じる(urequestsが例外発生時も開いたままの可能性があるため)
        if 'response' in locals() and response:
            response.close()

if __name__ == "__main__":
    try:
        wlan = connect_to_wifi(WIFI_SSID, WIFI_PASSWORD)
        wifi_data = get_wifi_data(wlan)
        
        if wifi_data:
            get_location_from_api(wifi_data)
        else:
            print("有効なAPデータが見つからなかったため、APIリクエストをスキップします。")
            
        wlan.disconnect()
        print("Wi-Fiを切断しました。")
        
    except Exception as e:
        print(f"\n致命的なエラーが発生しました: {e}")

このコードを保存して実行してみると、

Wi-Fi接続中: dummy SSID...
......
接続成功! IPアドレス: 192.168.1.1
周囲のWi-Fi APをスキャン中...
9件のAP情報を収集しました。

Geolocation APIにリクエストを送信中 (POST)...
--- 位置情報結果 ---
緯度 (Latitude): 35.02527
経度 (Longitude): 135.76222
推定精度 (Accuracy): 14.87 メートル
------------------
Wi-Fiを切断しました。

のような結果が出力されます。

京都の東本願寺で開催されているプログラミング教室で講師をしています。
詳しくはTera schoolを御覧ください。
大阪府高槻市でプログラミング教室を開設しています。
同じカテゴリーの記事
マインクラフト用ビジュアルエディタを開発しています。

詳しくはinunosinsi/mcws_blockly - githubをご覧ください。