【Python】息子のiPhoneの位置情報を取得して最寄り駅到着を通知する(pyicloud)

スマートホーム・IoT

息子のiPhoneはファミリー共有しているので、「iPhoneを探す」で居場所がわかります。塾で帰宅時間が遅くなる日に居場所がわかるのは便利ですが、自分でアプリを開かないと確認できません。

そこでPythonでコーディングすることで、例えば最寄り駅に着いた際に自動で通知を飛ばし、晩御飯の用意を始めるタイミングを教えてくれる仕組みを作りました。

通知はAlexa SDK(alexa-remote2等)を使ってAmazon Echoに喋らせていますが、そこは今回の範囲外です。通知部分の実装を工夫すれば、メール送信やLINE通知など色々応用できると思います。

前提条件

  • 親(自分)のApple IDでファミリー共有を設定済みであること
  • 子供のiPhoneで「iPhoneを探す」が有効になっていること(設定 > Apple ID > 探す > iPhoneを探す)
  • 親のApple IDで2ファクタ認証(2FA)が有効であること
  • 自宅にRaspberry Piなど常時稼働のLinuxマシンがあること

ファミリー共有を設定していれば、親のApple IDでログインするだけで、家族全員のデバイスの位置情報にアクセスできます。子供に特別な設定をお願いする必要はありません。

仕組みの概要

AppleのiCloudには「iPhoneを探す」の機能を外部から利用できる非公式APIがあり、Pythonライブラリ pyicloud を使うことでプログラムからアクセスできます。

  1. pyicloud でiCloudにログインし、「iPhoneを探す」の位置情報APIを叩く
  2. 取得した緯度・経度と、あらかじめ設定した地点(最寄り駅など)との距離をHaversine式で計算
  3. 指定半径以内に入ったら通知を実行
  4. cronで定期実行(5分間隔など)して監視

pyicloud はiCloudの各種サービス(写真、メモ、カレンダー等)にPythonからアクセスできるライブラリで、「iPhoneを探す」の位置情報取得にも対応しています。非公式ライブラリですが、長年メンテナンスされており実用的です。

実行環境

もともとAWS Lambda上で定期実行することを考えていましたが、どうもAppleの制限に引っかかるようで、クラウド環境からはうまく動きませんでした。そのため、自宅のRaspberry Piで動かしています。

AWS Lambdaで動かない理由

AppleはiCloudへのログインに対してIPアドレスベースの制限をかけています。AWSやGCPなどクラウドプロバイダのIPレンジからのアクセスは、不正利用対策としてブロックまたは追加認証を頻繁に要求されます。具体的には以下の問題が発生しました:

  • ログイン時に毎回2FAが要求される(セッションCookieが効かない)
  • IPアドレスが信頼されないため、セッションがすぐに無効化される
  • リクエスト自体がブロックされる場合もある

自宅の固定回線(住宅用ISP)のIPアドレスからであれば、一度認証すればセッションが長期間保持されます。Raspberry Piのように常時稼働できるマシンが最適です。

必要なもの

  • Raspberry Pi(モデルは問わない。Zero Wでも十分)
  • Python 3.11以上
  • pyicloud 2.6.5(timlaing fork)
  • 自宅のネットワーク接続(Wi-Fiまたは有線)

セットアップ

pyicloudのインストール

pip install pyicloud==2.6.5

バージョンは 2.6.5(timlaing fork)を指定してください。PyPIにある古い pyicloud はメンテナンスが止まっており、AppleのAPI変更に対応していません。timlaing氏がフォークした版が現在もアクティブにメンテナンスされています。

なお、依存ライブラリとして requestskeyringclick などが自動でインストールされます。

判定地点の座標を調べる

最寄り駅などの緯度・経度はGoogle Mapsから簡単に調べられます。

  1. Google Mapsで対象の場所を表示する
  2. 地図上で判定したいポイントを右クリック(スマホなら長押し)
  3. 表示されるメニューの一番上に緯度・経度の数値が表示される(例: 35.6812, 139.7671)
  4. クリックするとクリップボードにコピーされる

判定半径は、駅の改札から出口付近までをカバーできるよう300〜500m程度に設定すると使いやすいです。狭すぎるとGPSの誤差で検知できず、広すぎると通知が早すぎてしまいます。

サンプルコード

以下が位置情報を取得して距離判定を行うサンプルプログラムです。

"""
iPhoneの現在位置を取得し、指定地点に近い場合にログ出力するサンプル

必要ライブラリ:
  pip install pyicloud==2.6.5

初回実行時に2段階認証コードの入力が求められます。
認証後はセッションCookieが保存され、以降は再認証なしで実行できます。
"""

import math
import os

from pyicloud import PyiCloudService

# --- 設定 ---
ICLOUD_USERNAME = "your_apple_id@example.com"
ICLOUD_PASSWORD = "your_password"
TARGET_DEVICE_NAME = "iPhone"  # デバイス名の部分一致で検索

# 判定したい地点の座標と半径
TARGET_LATITUDE = 35.6812    # 例: 東京駅
TARGET_LONGITUDE = 139.7671
TARGET_RADIUS_M = 500        # 半径(メートル)

# セッションCookie保存先
COOKIE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".pyicloud_cookies")


def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
    """2点間の距離をメートルで返す (Haversine式)"""
    lat1, lon1, lat2, lon2 = map(math.radians, [lat1, lon1, lat2, lon2])
    dlat = lat2 - lat1
    dlon = lon2 - lon1
    a = math.sin(dlat / 2) ** 2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2
    return 6371000 * 2 * math.asin(math.sqrt(a))


def main():
    os.makedirs(COOKIE_DIR, exist_ok=True)

    # iCloudにログイン
    api = PyiCloudService(ICLOUD_USERNAME, ICLOUD_PASSWORD, cookie_directory=COOKIE_DIR)

    # 2段階認証
    if api.requires_2fa:
        code = input("2段階認証コードを入力: ").strip()
        if not api.validate_2fa_code(code):
            print("認証コードが無効です")
            return
        if not api.is_trusted_session:
            api.trust_session()

    # 対象デバイスを検索
    target = None
    for device in api.devices:
        status = device.status() if callable(device.status) else device.status
        if TARGET_DEVICE_NAME in status.get("name", ""):
            target = device
            break

    if target is None:
        print(f"デバイスが見つかりません: {TARGET_DEVICE_NAME}")
        return

    # 位置情報を取得
    location = target.location() if callable(target.location) else target.location
    if not location:
        print("位置情報を取得できませんでした")
        return

    lat = location["latitude"]
    lon = location["longitude"]
    print(f"現在位置: 緯度={lat}, 経度={lon}")

    # 距離判定
    distance = haversine_distance(lat, lon, TARGET_LATITUDE, TARGET_LONGITUDE)
    print(f"指定地点からの距離: {distance:.0f}m")

    if distance <= TARGET_RADIUS_M:
        print(f"*** 指定地点の {TARGET_RADIUS_M}m 以内にいます ***")


if __name__ == "__main__":
    main()

コードのポイント

  • Haversine式: 地球を球体と見なして2点間の最短距離(大圏距離)を計算する公式です。日本国内の数km程度の距離であれば十分な精度があります
  • cookie_directory: セッション情報の保存先を明示的に指定しています。これにより、cronなどUID/ホームディレクトリが異なる環境から実行しても確実にセッションを再利用できます
  • デバイス検索: TARGET_DEVICE_NAME を部分一致で検索しています。ファミリー共有では複数デバイスが表示されるため、子供のiPhoneを特定する名前を設定してください(例: 「太郎のiPhone」なら "太郎" と設定)

設定値の説明

定数説明設定例
ICLOUD_USERNAME親のApple ID(メールアドレス)parent@icloud.com
ICLOUD_PASSWORDApple IDのパスワード(アプリ用パスワードも可)
TARGET_DEVICE_NAME探したいデバイスの名前(部分一致)“太郎” や “iPhone 15”
TARGET_LATITUDE判定地点の緯度35.6812(Google Mapsで取得)
TARGET_LONGITUDE判定地点の経度139.7671(Google Mapsで取得)
TARGET_RADIUS_M判定半径(メートル)300〜500が目安

実行方法

python3 find_iphone_sample.py

初回実行時の認証

初回実行時には2段階認証コードの入力が求められます。

$ python find_iphone_sample.py 
2段階認証コードを入力: 

実行すると管理している親のiPhoneに以下のような2段階認証の通知が届くので、「許可する」をタップし、表示された6桁の確認コードをプログラムに入力します。

Apple Accountサインインが要求されました - 許可するかどうかの確認画面
Apple Account確認コード - 6桁の認証コードが表示された画面

一度認証すると、セッションCookieが .pyicloud_cookies/ ディレクトリに保存されるため、以降は再認証なしで実行できます。セッションは一定期間(経験上1〜2週間程度)で期限切れになりますが、その間は何度実行しても認証を求められません。

出力例

通常時(範囲外):

現在位置: 緯度=35.340118, 経度=139.488329
指定地点からの距離: 2450m

指定範囲内にいる場合:

現在位置: 緯度=35.6815, 経度=139.7670
指定地点からの距離: 35m
*** 指定地点の 500m 以内にいます ***

cronで定期実行する

Raspberry Pi上でcronを使って5分間隔で実行する設定例です。

crontab -e

以下の行を追加します:

*/5 * * * * /home/pi/venv/bin/python3 /home/pi/find_iphone_sample.py >> /home/pi/location.log 2>&1

ポイントとして、cronから実行する場合は python3 のフルパス(venv内のpython)を指定してください。環境変数PATHがcronでは通常と異なるためです。

重複通知を防ぐ工夫

5分間隔で実行すると、子供が駅の近くにいる間は何度も「範囲内にいます」と通知されてしまいます。これを防ぐには、前回の判定結果をファイルに保存して、状態が変化したときだけ通知を飛ばすようにします。

# 前回の状態を読み込み
STATE_FILE = "/home/pi/.iphone_location_state"

def load_state():
    try:
        with open(STATE_FILE) as f:
            return f.read().strip()
    except FileNotFoundError:
        return "outside"

def save_state(state):
    with open(STATE_FILE, "w") as f:
        f.write(state)

# 距離判定後に状態変化をチェック
current_state = "inside" if distance <= TARGET_RADIUS_M else "outside"
previous_state = load_state()

if current_state == "inside" and previous_state == "outside":
    # 範囲外→範囲内に変化したときだけ通知
    print("*** 通知: 最寄り駅に到着しました ***")
    # ここに通知処理を書く

save_state(current_state)

このように「範囲外から範囲内に入った瞬間」だけ通知することで、何度も同じ通知が飛ぶのを防げます。

トラブルシューティング

「位置情報を取得できませんでした」と表示される

  • 子供のiPhoneの電源が入っているか確認する
  • 子供のiPhoneがネットワーク(Wi-Fiまたはモバイルデータ)に接続されているか確認する
  • 「iPhoneを探す」が有効になっているか確認する(設定 > Apple ID > 探す)
  • iPhoneが「機内モード」になっていないか確認する

毎回2FAを求められる

  • cookie_directory が正しく設定されているか確認する
  • cronで実行している場合、実行ユーザーのホームディレクトリとcookie保存先が一致しているか確認する
  • クラウド環境(AWS/GCP等)から実行していないか確認する。自宅ネットワークから実行する必要がある

「デバイスが見つかりません」と表示される

  • TARGET_DEVICE_NAME がデバイス名に含まれている文字列か確認する
  • iPhoneの名前は「設定 > 一般 > 情報 > 名前」で確認できる
  • ファミリー共有が正しく設定されているか確認する

セッションが切れた場合の対処

セッションCookieの期限が切れると、cron実行時に requires_2fa が True になり、対話的な入力が必要になります。手動で一度実行して2FAコードを入力すれば、再びセッションが保存されます。

# 手動で再認証
$ python3 /home/pi/find_iphone_sample.py
2段階認証コードを入力: (iPhoneに届いたコードを入力)

自動化を徹底するなら、セッション切れを検知してメールなどで通知する仕組みを追加するのも良いでしょう。

注意事項

  • 非公式API: pyicloudはApple非公式のライブラリです。AppleのAPI仕様変更により突然動かなくなる可能性があります。ただし、コミュニティで活発にメンテナンスされているため、大きな変更があっても比較的早く対応されます
  • 実行環境: AWSなどクラウドのIPアドレスからはAppleにブロックされます。自宅ネットワーク内(Raspberry Piなど)で実行してください
  • pyicloudのバージョン: 2.6.5(timlaing fork)を使うこと。 pip install pyicloud だけだと古い別のバージョンが入る場合があるので、バージョン指定を忘れずに
  • Apple IDのパスワード管理: サンプルコードではベタ書きしていますが、本番運用では環境変数(os.environ)やファイルの権限設定(chmod 600)で保護してください
  • 位置情報の精度: GPSの精度は環境に依存します。屋内や地下では誤差が大きくなることがあります。判定半径を広めに設定することでカバーできます
  • プライバシー: 位置情報の取得は家族間の合意のもとで行ってください。子供に無断で監視するのではなく、安全のための仕組みとして話し合ったうえで使うことを推奨します

通知の応用例

今回はログ出力のみですが、距離判定の部分を拡張すれば様々な通知が可能です。

  • Amazon Echo(Alexa): Alexa SDKを使って音声で「もうすぐ帰ってくるよ」と喋らせる。キッチンで料理中でも気づける
  • LINE通知: LINE Notify APIでスマホにプッシュ通知。外出中でも確認できる
  • メール: smtplibで家族にメール送信
  • スマートホーム連携: Nature RemoやSwitchBotと組み合わせて、帰宅前にエアコンや照明をONにする
  • 複数地点の監視: 最寄り駅だけでなく、塾の最寄り駅、自宅付近など複数の判定ポイントを設定して、移動の進捗を追跡する

まとめ

pyicloudを使えば、ファミリー共有しているiPhoneの位置情報をPythonから簡単に取得できます。Raspberry Piとcronを組み合わせて定期実行すれば、「最寄り駅に着いたら通知」のような自動化が手軽に実現できます。

AWS Lambdaではうまく動かなかったのが残念ですが、自宅のラズパイで十分実用になっています。通知先をAlexaにすることで、キッチンにいながら息子の帰宅タイミングがわかるのは地味に便利です。

初期設定は多少手間ですが、一度動いてしまえばほぼメンテナンスフリーです。セッションが切れたときだけ手動で再認証すれば、あとは自動で動き続けます。お子さんの帰宅管理に悩んでいる方は、ぜひ試してみてください。

コメント

タイトルとURLをコピーしました