ローソンに行かなくても!?povo Data Oasisを自宅からデータゲット
この記事は技術実験の記録として共有するものです。実際のサービス利用規約に反する可能性があるため、内容の実装は推奨しません。あくまで技術学習の一環としてご理解ください。
🌵 Data Oasisって何?
まず簡単におさらいすると、povo2.0のData Oasisは非常にお得なサービスです!ローソンに行って位置情報をチェックインするだけで、毎日0.1GB(100MB)のデータ容量がもらえるんです。月に最大10回まで利用可能なので、毎月最大1GBが無料でゲットできます!
ただ問題は...毎日ローソンに行くのって大変ですよね?「今日もデータゲットしなきゃ」と思いながらも、雨の日や忙しい日は諦めることもしばしば...
そこで私の技術者魂が目覚めました!「自宅からでもデータがもらえないだろうか🤔」
🔍 APIを解析してみる冒険
まず、povoのData Oasisがどのように動作しているのかを調査しました。実は、このサービスはウェブサイトからも利用できるのです。
https://shop.povo.jp/dashboard/povo-segment-1?clientid=XEmOoDzmj3Uo6TPGHkU7ChignFq8h7xg
このページにアクセスすると、「データをチャージする」というボタンがあり、クリックするとブラウザが現在地の位置情報を要求します。そして、その位置データをサーバーに送信して、あなたが本当にローソンにいるかどうかを確認するのです!
興味深いことに、この仕組みは位置データのみを確認するシンプルな設計です。つまり、位置データを制御できれば、自宅からでもデータ取得が可能になるかもしれません。
🧠 認証システムを理解する
本格的に自動化を実装するためには、まずpovo APIの認証システムを理解する必要があります。povoはAuth0という業界標準の認証サービスを利用しています。これはOAuthベースのシステムで、以下の二つの重要なトークンを使います:
最初にpovo APIと通信するためには、この認証フローを実装する必要がありました。以下はそのコアとなる部分です:
class TokenManager: def __init__(self): self.tokens = {} self.load_tokens() def refresh_token(self): """アクセストークンとリフレッシュトークンを更新""" headers = {"Content-Type": "application/x-www-form-urlencoded"} # 現在のリフレッシュトークンか初期リフレッシュトークンを使用 refresh_token = self.tokens.get("refresh_token", INITIAL_REFRESH_TOKEN) payload = { "grant_type": "refresh_token", "refresh_token": refresh_token, "client_id": AUTH0_CLIENT_ID } response = httpx.post(AUTH0_URL, data=payload, headers=headers) self.tokens = response.json() self.tokens["created_at"] = datetime.now().timestamp() self.save_tokens()
アクセストークンはJWT(JSON Web Token)形式で提供され、これをデコードして有効期限を確認するロジックも実装しました:
def get_expiry_time(self): """アクセストークンから有効期限を取得""" if not self.tokens or "access_token" not in self.tokens: return datetime.now() access_token = self.tokens.get("access_token") # JWTをデコード(署名は検証しない) payload = jwt.decode(access_token, options={"verify_signature": False}) if 'exp' in payload: # UNIXタイムスタンプを日時に変換 expiry_time = datetime.fromtimestamp(payload['exp']) return expiry_time
トークンの自動リフレッシュメカニズムも実装しました。これにより、常に有効なアクセストークンを維持できます:
def should_refresh(self): """トークンを更新すべきかどうかを判断""" expiry_time = self.get_expiry_time() # 5-10分前に更新 early_refresh = timedelta(minutes=random.randint(5, 10)) return datetime.now() + early_refresh >= expiry_time
これらの実装により、一度初期設定さえすれば、数ヶ月間にわたって自動的に認証を維持できる仕組みが完成しました。特に注目すべきは、トークンをローカルファイルに保存することで、プログラムを再起動しても認証状態を維持できる点です。
📍 povoにローソンにいると信じさせる魔法
最も興味深かった課題は、位置情報を操作し、povoサーバーに実際にローソン店舗内にいると認識させることでした。ブラウザから送信される位置データを、実際のローソン店舗の座標に変更する必要があります。

まずは実際のローソン店舗の座標を取得し(LOCATION_BOUNDS)、その範囲内でランダムな位置を生成します:
def generate_random_location(): """ローソン店内のランダムな座標を生成""" # 店舗の四隅の座標から範囲を計算 lat_min = min(point[0] for point in LOCATION_BOUNDS) lat_max = max(point[0] for point in LOCATION_BOUNDS) lng_min = min(point[1] for point in LOCATION_BOUNDS) lng_max = max(point[1] for point in LOCATION_BOUNDS) # ノイズを加える random_lat = random.uniform(lat_min, lat_max) random_lng = random.uniform(lng_min, lng_max) lat_noise = random.uniform(-0.000005, 0.000005) lng_noise = random.uniform(-0.000005, 0.000005) return [random_lng + lng_noise, random_lat + lat_noise]
🚀 データ受け取りの実装
最も重要な部分は、実際にpovo APIと通信してデータをゲットする関数です。この claim_reward 関数が全システムの心臓部といえるでしょう:
async def claim_reward(token_manager): """データ報酬を受け取るAPI呼び出し""" access_token = token_manager.get_access_token() if not access_token: return "有効なアクセストークンがありません。" headers = { "Accept": "application/json", "Authorization": f"Bearer {access_token}", "Sec-Fetch-Site": "same-site", "Accept-Language": "ja", "Sec-Fetch-Mode": "cors", "Content-Type": "application/json; charset=utf-8", "Access-Control-Allow-Credentials": "true", "Origin": "https://kddi-openapi-web.povo.jp", "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_3_1 like Mac OS X) AppleWebKit/605.1.15...", "Referer": "https://kddi-openapi-web.povo.jp/", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty" } # ローソン店内のランダムな位置を生成 random_location = generate_random_location() data = {"location": random_location} try: async with httpx.AsyncClient() as client: response = await client.post(CLAIM_URL, json=data, headers=headers) result = response.json() return f"データゲット成功!\n座標: {random_location}\n応答結果: {json.dumps(result, indent=2, ensure_ascii=False)}" except Exception as e: return f"データゲット失敗: {str(e)}"
特に注意したのは、本物のブラウザからのリクエストと見分けがつかないよう、User-Agent、Referer、その他のヘッダー情報を正確に設定している点です。これにより、サーバー側の異常検知をパスできます。
📊 実際の運用結果
実際に使ってみると、想像以上にうまく機能してくれました!さらに、システムの利便性を高めるために、LINE Botも実装し、自分のサーバーにデプロイしました。これにより、パソコンの前にいなくても、スマホからいつでもどこでも操作できるようになりました!
LINE Botの実装により:
- 外出先からでもワンタップでデータ受け取り可能
- 受け取り結果をリアルタイムで確認
- 自動化と手動操作を併用できる柔軟性
実装したLINE Botは非常にシンプルなインターフェースで、「データゲット」ボタンを押すだけで即座にpovoの追加データが受け取れます。サーバーに常時稼働させているので、24時間365日いつでも利用可能です。
このシステムではバックグラウンドでトークン管理も自動化されているため、一度設定すれば数ヶ月間メンテナンス不要で運用できます。「今日もローソンに行かなきゃ」というストレスから完全に解放されました!移動中や会議中でも、LINE一つで簡単にデータ追加ができるようになり、生活がより便利になりました。
😎 まとめ
技術の力によって日常生活がより便利になりました。毎日ローソンへ出向く手間が省け、朝起きると自動的に100MB追加されているという快適さを実感しています。月に1GBの追加データは、特に繁忙期には大きな助けになります。
ではまた次回のブログでお会いしましょう!
B-Routeサービスの魅力に迫る:ハードウェアとソフトウェアの準備完全ガイド
序章
最近引っ越しをしたので、以前から興味があったB-Routeサービス(電力メーター情報発信サービス)を試してみることにしました。東京電力の管轄内に住んでいるので、直接東京電力に申し込んでB-Routeサービスを開始しました。これで、自分のIoTデバイスを通じて、自宅の電力消費量や累計のキロワット時数をリアルタイムで取得できるようになります。
ハードウェアの準備
東京電力は迅速にB-Routeサービスを開始してくれました。認証IDとパスワードも受け取りました。次は、デバイスの準備です。最初は、検索エンジンでB-Route + Arduinoと検索し、ロームのBP35A1モジュールを使用したいくつかのソリューションを見つけました。簡単に評価した結果、実行可能だと判断しました。そこで、BP35A1モジュールを販売しているディーラーを探し始めましたが、税込みで8000円以上もすることがわかりました。そこで、中古市場で安いものがないか探してみると、IIJのクラウドサービスが廃業したことで市場に出回っているIoTデバイス「SA-M0」を見つけました。これはシングルボードコンピュータのソリューションで、BP35A1モジュールが内蔵されている点が鍵です。ヤフオクで3000円で手に入れることができたので、即購入し、このデバイスの研究を始めました。
購入前に、このSA-M0は本来IIJのクラウドサービスと連携して使用する必要があることを知っていましたが、IIJが廃業したため、現在中古市場ではジャンク品として扱われています。最悪の場合、シングルボードコンピュータ自体が使えず、モジュールを取り外して単独で使用する必要があるかもしれません。しかし、運が良ければ、この記事のケースのように、シングルボードコンピュータも利用でき、追加のハードウェアを節約し、個人的にはArmadillo-Box WS1ベースのSA-M0は、回路設計や筐体設計が非常に精巧な商業レベルの製品で、多くのオープンソースソリューションよりも優れていると考えています。そのため、このシングルボードコンピュータを使ってスマートメーターとのやり取りを行うことにしました。
ソフトウェアの準備
まずはshellアクセスを試みます。Armadilloの公式ウェブサイトのマニュアルによると、このマシンはBusyboxのLinuxを搭載しています。公式マニュアルの手順に従ってshellに成功裏にアクセスしました。
ここでちょっとしたエピソードがあります。私はESP32からマイコンに触れ始めたので、通信に関する理解はTTLレベルのUARTに留まっていました。しかし、このSA-M0はRS232レベルなので、USB->UARTアダプタに接続するためにはレベル変換が必要でした。

[root@abws1-0 (pts/0) ~]# uname -a Linux abws1-0 3.14.36-at11 #1 PREEMPT Sat Mar 31 02:35:12 JST 2018 armv5tejl GNU/Linux [root@abws1-0 (pts/0) ~]# cat /proc/cpuinfo processor : 0 model name : ARM926EJ-S rev 4 (v5l) BogoMIPS : 133.00 Features : swp half thumb fastmult edsp java CPU implementer : 0x41 CPU architecture: 5TEJ CPU variant : 0x0 CPU part : 0x926 CPU revision : 4 Hardware : Armadillo-420 Revision : 0300 Serial : 0000000000000000 [root@abws1-0 (pts/0) ~]#
当初は、B-Routeのプロトコルを自分で調べて小さなプログラムを書いてみようと思っていましたが、Githubを軽く検索すると、既にたくさんのソリューションがありました。その中からPythonで書かれたプログラムを使ってみることにしましたが、このマシンに実行環境を整えるのが結構大変だと気づきました。最終的には、コンパイル型のソリューションを使うことに決めました。例えばGoやRustなどです。Githubでオープンソースのプロジェクトもあり、早速試してみると、とても使いやすかったですが、そのプロジェクトはデータの読み取りのみで、どうやらexecを使ってshellスクリプトを実行し、zabbixのようなサービスに送信しているようです。しかし、私たちが必要なのはHome Assistantに直接データを取得してもらうことです。色々と研究した結果、万能のmqttを橋渡しとして使い、接続を完了することにしました。
コードの部分は非常にシンプルです。ほとんどの部分は元のリポジトリの実装を参考にすることができるので、mqttの部分を追加するだけでよかったのです。最後にパラメータを設定し、起動時に自動で実行されるようにすると、大きな成果が得られました。

参考文献
NURO MAP-E と OpenWRT:安定した効率的なネットワーク接続を構築
イントロ
NUROが提供するHGW(ホームゲートウェイ)にうんざりしました。ブリッジモードに変更できず、ファームウェアの機能も散々です。さらに、最初に契約したのは2024年2月で、その時にはSGP200WというHGWを受け取っていました。それはIPv4+IPv6のデュアルスタックDHCPでした。しかし、1か月後に2.5gをサポートするNSD-G1000Tを試したくなり、変更を申請したところ、密かにMAP-Eに変更され、専用IPv4とお別れしました…。ソニーのマーケティング末期に遭遇したのに、それでも逃してしまいました。HGWとMAP-Eのダブルデバフの下で、自分の機器に置き換えることを考えました。
準備
最初は先輩に相談し、彼のTL-ER2260Tのプランをそのまま採用する予定でした。しかし、彼自身はまだデュアルスタックを使用しているため、TL-ER2260TはOpenWRTの公式サポートがなく、非公式のクローズドファームウェアしかありません。ソフトウェアリポジトリも共有できないため、選択しませんでした。その後、QNAPのqhora-301wを調査しましたが、この製品の10gポートはRJ45口で、2024年3月の時点でOpenWRTがこのルーターをサポートするファームウェアの対応度は良くありませんでした。これらの機器はすべて中国から輸入する必要があり、チャンスは一度しかないという原則に基づいて、最終的には安定を求めてx86ソフトウェアルーターを選択しました。さらに、GPON ONUスティックもどれが使えるか分からないため、ODI、Huawei、Nokia、Alcatelの4つのメーカーの製品をそれぞれ1つずつ購入しました。
ハードウェアの選定
実際、全工程で最も難しいのは選定の部分です。その後、機器がすべて届いたら、箱を開けて商品を確認します。気づけば、スイッチを選ぶのに失敗していました。1つのSFP+ポートと8つのRJ45ポートを選ぶべきだったのに、2つのSFP+ポートと4つのRJ45ポートを選んでしまいました。しかし、2台をつないで使用すれば問題なく使えます。 GPON ONUスティックはまずODIを試しました。最も高価だったためです。しかし、元のファームウェアが原因で、設定が正しくなかったようで、O5の問題が解決できませんでした。しかし、他のブログでODIで成功してO5を達成した事例も見ました。その後、HuaweiとNokiaに切り替えました。どちらも自作のOpenWRTファームウェアで、既にNSD-G1000Tに切り替えていたため、GPON SNを設定すれば完璧にO5を達成できました。Alcatelのものは電源を入れずに箱に戻しました…。

コンセントの節約を考えて、GPON ONUスティックとメディアコンバーターを使用せずに、直接スティックをスイッチに挿入し、スイッチのVLANを設定してWANとLANを分離しました。そして、スイッチの2つのポートにそれぞれx86ソフトウェアルーターのWANとLANを接続しました。他のポートはすべてLANポートとして設定し、2年以上使用していたWiFiルーターもついにブリッジとして引退しました。配線と整理の作業を経て、いよいよ電源を入れて実験を開始しました。
MAP-E
まず、x86ソフトウェアルーターにOpenWRTをインストールします。ここでは、公式のファームウェアセレクターの製品ファームウェアを直接選択します。期待通りにDHCPのIPv6アドレスを取得できます。ここでopkgを通じてmapパッケージをインストールし、このスクリプトを使用してMAP-Eに必要なパラメータを自動生成します。この点ではリポジトリの開発者に感謝です。これでIPv4も正常に動作するはずです。ここで少し余談ですが、以前OpenWRTを使用したことがなかったため、多くの部分で最初はどのように設定すればいいのか分かりませんでした。MAP-Eを完了した後、IPv6アドレスを取得できないことに気づき、最初はそのスクリプトの問題かと思いました。しかし、最終的には、IPv6アドレスを割り当てるためのプロトコルを設定していないことが原因であることが判明しました。SLAACかDHCPv6のいずれかを設定していないため、調査の結果、SLAACを設定してネットワークを正常に復旧させました。

IPv4 && Ping
ここまで順調だと思っていたのですが、IPv4のPingが動作しないことに気づきました。しかし、IPv6のPingは正常に動作していました。そこで調査を進めたところ、こちらに解決策が見つかりました。日本のISPのMAP-Eプロトコルの一部がmapパッケージで処理されていない部分があるようです。そのため、コードを手動で修正する必要があります。
IPv6 && Android
WiFiに接続したAndroidデバイスでは、数時間後にIPv6接続が失われます。WiFiを一度切断して再接続することで正常に戻りますが、数時間後に再び失われる現象が起こります。この問題は他のルーターを使用していたときにも経験しましたが、その時はルーター自体のファームウェアが不十分で深く追求しませんでした。今はOpenWRTを使用しているので、しっかり調査する時が来ました。この問題の原因はシンプルです。Androidがディープスリープモードに入ると、RA(ルーターアドバタイジング)を失います。RAのライフタイムがデフォルトで1500秒であり、ディープスリープ後にライフタイムが切れると、新しいIPv6アドレスを取得できなくなります。この問題の解決策も簡単です。RAのライフタイムを最大値の9000秒に変更すれば、問題を解決できます(完全に解決するわけではありませんが、発生する頻度が大幅に減少します)。
参照
掃除ロボットをクラウドの奴隷にさせない - Valetudoで制御権を取り戻す
序文
今年の初めに、メルカリでDreame L10Proというロボット掃除機を購入しました。その際、Valetudoプロジェクトに興味を持ち、ロボットを改造しようと考えました。ロボット掃除機が届いた後、ドキドキしながらドキュメントに従い、ルート権限の取得を試みました。しかし、残念ながら中古品で、以前のユーザーがファームウェアを最新バージョンにアップグレードしていました。当時、最新バージョンのファームウェアのルート化方法は公開されていなかったため、断念せざるを得ませんでした。
しかし
当時、いくつかのルート化方法は非公開で、Valetudoプロジェクトチームに直接連絡して詳細を入手する必要がありましたが、うまく進展しませんでした。最近、新しいルート化方法が公開され、物理的なツールを併用する必要があることが分かりました。Valetudoプロジェクトチームは主にヨーロッパ地域で活動しており、アジア地域のユーザーには支援が難しい状況でした。しかし、新しい方法が公開されたことで、私はロボットの改造方法を研究し始めました。
徹底的な研究
実は、最初は新しいルート化方法がDEFCON 31と一緒に公開されたことを知りませんでした。日常的にコミュニティのメッセージを見ていたところ、最近のValetudoユーザーグループの活気ある議論から新しいルート化方法が公開されたことを知りました。それで、大いに興奮し、詳細を調査し始めました。実際のところ、変更は大したことはありませんでした。私のDreame L10Proの古いファームウェアバージョンでは、UARTラインをコンピュータに接続してシリアル通信を介してシェルにアクセスする方法でした。その際に問題になったのは、シェルがスポーンされなかったことです。新しいファームウェアでは、同じくUARTラインを接続し、以前は使用されていなかったUSB OTGラインを同時に接続すると、新しいファームウェアはUSBデバイスが接続されている場合、シェルがスポーンされるようになります(さらにはrootfsがマウントされますが、それは重要ではありません。私たちはただシェルが必要です)。 原理を大まかに理解したら、詳細な研究に取り掛かりました。まず、USBメスケーブルとUSBメモリを用意する必要がありました。ジャンパーワイヤーやシリアルチップなどは既に家にあったので、これ以上用意する必要はありませんでした。近くの百元店でUSB延長ケーブルとUSBメモリを購入しました。
原理を大まかに理解したら、詳細な研究に取り掛かりました。まず、USBメスケーブルとUSBメモリを用意する必要がありました。ジャンパーワイヤーやシリアルチップなどは既に家にあったので、これ以上用意する必要はありませんでした。近くの百円ショップでUSB延長ケーブルとUSBメモリを購入しました。

これで準備が整いました。ただし、USBケーブルの処理が必要です。ジャンパーワイヤーヘッドを接続するために、はんだごてを使うのが面倒だったので、ワニクリップなどの道具を使って固定しました。マルチメーターで各ピンが正しく接続され、短絡していないことを確認し、いよいよ作業を開始しました。

次にUSBからシリアルに変換する作業です。家に専用のUARTアダプタがなかったので、一時的にesp32を使用しました。esp32-devkitにはUSBからシリアルに変換するチップが内蔵されていました。ここでは、esp32のENピンをGNDに接続し、動作を停止させ、外部回路のみを使用する必要があります。

最後に、ドキュメントに示されている変換カードのPCBデザインから接続を導き出しました。接続する必要があるピンは以下の通りです:
- TX -> TX(ここではRXと交換することが必要な場合もあります。私はesp32-devkitの回路を使用しているため)
- RX -> RX
- GND -> GND(ここでは一本の接地線のみを接続しました。他の部分はすべてブレッドボード上で共通の接地を持っています)
- USB_OTG_ID -> GND(この線は共通の接地に直接引き出されます)
- D+ -> D+(以下の3つのすべてがUSBケーブルですが、特に特別なことはありません)
- D- -> D-
- VBUS -> VBUS

最終的な外観と、成功したルートアクセス。

ルートアクセスを手に入れた後は、簡単です。元のデータをバックアップし、Valetudoをフラッシュしてクラウドから独立した完全なローカルコントロールを実現しました。

参考文献
https://web.archive.org/web/20230131072513/https://valetudo.cloud/pages/installation/dreame.html https://robotinfo.dev/detail_dreame.vacuum.p2029_0.html https://chaos.social/@fizzo/110887346585058373 https://medium.com/@shelladdicted/how-to-use-an-esp32-devkit-as-an-uart-adapter-e698594e0378