# Oura Ring Gen3 を買った

- URL: <https://krymtkts.github.io/posts/2022-12-25-bought-ouraring-gen3.html.md>
- Date: 2022-12-25
- Author: krymtkts
- Tags: ouraring, powershell

前から欲しかった [Oura Ring](https://ouraring.com/) を購入した。 買ったのは第 3 世代(Gen3)だ。

### 事の起こりから購入まで

ほしいな～と思い出したのは大体 1 年くらい前。 [TechCrunch で Oura Ring 3 review](https://techcrunch.com/2021/12/02/oura-ring-3-review/) を読んだらへん。
スマートリングにも色々あるが、 NFC がついたようなのはあまりに仰々しく邪魔そうでナシ。単純にバイタルデータを取るだけのために腕時計型のウェアラブルデバイスをつけるのも好きじゃない。 Oura Ring はちょうど良い感がある唯一のデバイスとして気になり始めた。

2022-04 から勤めている現職がヘルステックなのもあって、健康関連の経費補助(満額ではないが)があるので、転職を機にこれは買うしかあるまいなと心を決めた。
また、過去に Gen2 から Gen3 への転換期に[買い替えユーザのメンバーシップを無償にするオファーがあったらしい](https://rcmdnk.com/blog/2021/11/01/computer-iot-shopping/)。
当面は経費補助で落とすにしても、いつかチャンスが来るかもということで更に背中を押してくれた。

ところが、 2022 年は記録的な円安になったこともあり、思い立った 5 月頃からずっと変えずにいた。もう仕方がないので 12 月になってやっと自分用のクリスマスプレゼントという建前で多少高くても買うことにした。

マットでシンプルな [Horizon Stealth](https://ouraring.com/product/horizon-stealth) がいいかなーと思ったが、為替によるパンチ力を極力抑えるために、[Heritage Black](https://ouraring.com/product/heritage-black) にした。

Oura Ring の購入は 12/05 。指輪のサイズなんかわからないので Sizing Kit ありで購入。購入時のコストは以下の通り。

- 購入価格 43,200 JPY
  - 本体 299.00 USD
  - 送料 15.00 USD
  - 換算レートは 2022-12-06 で 1 USD = 137.581 JPY
- プラス関税で DHL に 3,400.00 JPY
- 合計 46,600 JPY

決して安くない買い物だ。経費補助の残額的に 17,000 円位は補填できるが、自腹で 3 万かー。ちょうどこの時期日本ではソフトバンクでの取り扱いが始まったが、そっちよりは安い(ソフトバンクで買う意味あるんかが謎)。
このガジェットはゴリゴリに使い倒さねばなるまい。

因みに Heritage Black は、商品ページで見るよりもかなりメタリックだ。黒というよりもガンメタリックでちょっと想像よりもチャラかった。

### 購入から本体到着まで

購入後の流れは非常にスムーズだった。概要は以下の通り。

- 12/05 購入
- 12/07 Sizing Kit 到着。 9 で検証
- 12/08 8 で検証
- 12/09 8 で確定
- 12/12 本体到着

購入の 12/05 から 2 日後の 12/07 に Sizing Kit が届く。
Oura 推奨の利き手の逆の人差し指だとギターが弾けなくなるので、右手人差し指を基準をに考えて 9 で検証はじめた(実際に左手人差し指につけてギターを弾いたら指の腹でネックを握れないのでこの判断は正しい)。
ところが翌朝まで 1 日つけてみていまいちなのではと思い始めた。 9 は朝の時間帯でも少し緩い。

利手の右手だと、皿洗いや風呂洗い等あらゆる家事をする時にぶつかる、また食事の時は箸にぶつかるなど、大いに支障があるとわかってきた。左手だと皿洗い時に食器の底の凶悪なザラザラに接触する機会があるのだが、他はさして支障にならなかった。
(Oura Ring はチタン製らしい。モース硬度 4 と仮定すると茶碗などの磁器はモース硬度 7 相当であることを考えれば、皿洗いで Oura Ring が傷つくのは想像するに易い)

同時並行して、使用レビューを Web で漁り、体の変化等で人差し指が多少きつめになったときでも中指に逃げる運用があると知った。実際に自分の人差し指のサイズだと左右で結構差があるが、左手だと人差し指と中指は 8 で運用できるのがわかった。右手人差し指にずっと 8 をはめているのは窮屈だが、ギターを引くときだけはめるとかなら全然いける。
なのでサイズは 8 で決定した。

決めたのは 12/09 で、待ちに待った本体到着が 12/12 。普段海外のサプリ購入で感じてるリードタイムに比べると相当に早かった。

### 運用を始めてみた

12/12 の午後くらいから運用している。
つまり本日 12/25 時点で およそ 2 習慣運用した上での感想を記しておく。

選択している focus area ? は `Be productive and energetic` 。なんとなく。これ他のも試してみたいがトレンド？のビューが変わる以外どこに影響あるかわかってない。

Readiness, Sleep, Activity いずれも概ね 90% 前後で推移しており、数値上は非常に健康であると言えるだろう。

というのも、 Oura アプリの Insight ？ 画面が非常によくできていて、スコアを落とすことに何らかの罪悪感のようなものを抱かせるようになっているからだ。
例えば、全体的に青い色調のビジュアルをしているが、スコアが低い要素は赤くなって不安感を煽られる(現に 「Pay attention」 と言われるし)。
また Activity が取れていないとアプリの通知で「足をストレッチする時間ちゃうか？」と ~~~脅して~~ 教えてくれるので、強制的に動くような生活リズムが形成されてくる。これが自身で設定した focus area に対応した ~~仕打ち~~ アプローチなのかは不明だが。

先にアメとムチで言うところの鞭面について触れたが、アメ面でいえば Readiness, Sleep, Activity の各スコアで 85 以上を叩き出せばクラウンをもらうことができる。クラウンはスコアの横にちょこんと表示されるのだけでなく、カレンダーでそれまで得たクラウンを確認することもできる。
クラウンを得たとてどうということないのだが、ゲーマーなら一種のトロコンのような満足感を得られるだろう。現に 1 週目はクラウンが 1 つの日もあったが、 2 週目は最低でもクラウン 2 つ。来たる 3 週目は間違いなく全冠とれるだろう。~~アプリに人間が操られていると言っても過言ではない。~~

お陰で装着 1 週目から、 Readiness と Sleep のスコアを改善するために夜更かしせず寝ると決めたら即寝る、 Activity のスコアを改善するために 1 時間毎にこまめに運動する等、生活習慣を改善し始めれた。このまま継続していけば、その手に勝利をつかむ日も遠くないだろう(何と戦ってる？)。

あと 3 週程で無料のメンバーシップ期間が切れてしまうのだけど、その時アプリがどれだけ使いにくくなるのか検証してみる。それでメンバーシップの継続をするか決めたらいい。現状のレポートに結構満足してるので、多分継続しそうやけど。

台座みたいな充電器は USB Type-C を接続するやつなのだけど、中の基盤が遊んでるっぽくてちょっとガタツキがある。 Oura Ring 自体はそうそう壊れそうに無いけど、充電器の方は丁寧に扱わないと割とすぐ逝ってしまわれるかも。

### プログラマからみた Oura Ring の楽しみ方

もう 1 つ Oura Ring の楽しめる点がある。それは API が充実している点だ。

ほんまか知らんがアプリで提供される数値は全て API で取ることができるらしい。名称の表記が Oura Cloud API だったり Oura API だったりどっちやねんというところではあるが、ここでは簡単のため Oura API と呼ぶことにする。
現状 Oura API には [Oura API V1](https://cloud.ouraring.com/docs/) と [Oura API V2](https://cloud.ouraring.com/v2/docs) が存在する。 V1 は 2023 年初頭に廃止されるらしい。もうすぐやないか。

> The current timeline to sunset the Oura API V1 is early 2023. We are not planning any updates—outside of required maintenance—to the Oura API V1 moving forward. A migration guide for users to transition from V1 to V2 of the Oura API will be available in February 2022.

API を利用するにあたり認証の手段は 2 つある。
1 つは Personal Access Token 、 もう 1 つは OAuth の API Application だ。
自分のデータにアクセスする場合は Personal Access Token で良いが、複数ユーザのデータを扱うような場合は API Application を使うらしい。でもデフォでは MAX 10 人しか扱えないようなので、より多くのユーザを扱う場合には審査を経て承認が必要だと。はいはい 〇〇 Ads とかの API 利用でもそういうのあったわ。

Oura API V2 では [OpenAPI spec](https://cloud.ouraring.com/v2/static/json/openapi.json) が公開されているので、アプリに組み込むだとかのときも安心に使えそう。
Open API Generator とかで生成したやつが使いやすいかどうかは Schema が丁寧に作られてるか次第だったりするので、いつか試してみるのも良いだろう。

今日この記事を書くにあたり、 Personal Access Token を用いて自分のデータを取得してみた。

まずは [Personal Access Tokens - Oura Developer API](https://cloud.ouraring.com/personal-access-tokens) で Personal Access Token を発行する。

疎通確認の為に、自分の Personal Info を取得してみる。 Oura API V2 は Stripe の API Document のように激イケではないものの、見た目はそこそこ親切で好感が持てる。
[Get Personal Info](https://cloud.ouraring.com/v2/docs#operation/personal_info_route_get) に cURL の例があるのでそれをコピり、 PowerShell 用に書き換える。

```sh
curl --location --request GET 'https://api.ouraring.com/v2/usercollection/personal_info?start_date=2021-11-01&end_date=2021-12-01' \
--header 'Authorization: Bearer <token>'
```

ドキュメントには書いてないクエリ文字列がついててなんか怪しい。
試しに今月始めから今までで取得する。といっても配列が得られるわけではないらしい。期間指定するのはなんでだ？まあいい。

```powershell
$Token = ConvertTo-SecureString "!!!PERSONAL_ACCESS_TOKEN!!!" -AsPlainText
Invoke-RestMethod -Method Get -Uri "https://api.ouraring.com/v2/usercollection/personal_info?start_date=$((Get-Date -Day 1).ToString('yyyy-MM-dd'))&end_date=$((Get-Date).toString('yyyy-MM-dd'))" -Authentication Bearer -Token $Token

# こんなのが取れる
# age            : 39
# weight         : 61
# height         : 1.8
# biological_sex : male
# email          : メアド
```

ドキュメントによると身長は 1m を 1 と扱う。体重は Kg らしい。ただ謎なのは 62 Kg で登録してるのに何故 61 ? 連携してるサービス由来か？と思ったが Google Fit も 62 、 Health Connect はどうかわからない。もしやゼロオリジン？わからん。今後調査を進めよう。

配列じゃないのになんで日を指定するんやろか、と思ってパラメータ削って実行してもデータ取れた。これドキュメントのサンプルコードが間違ってそうやな。ドキュメント自体はあってるので単にケアレスミスやろか。

```powershell
Invoke-RestMethod -Method Get -Uri "https://api.ouraring.com/v2/usercollection/personal_info" -Authentication Bearer -Token $Token

# 取れるんかい！
```

認証できない時どうなるんだろうと思って `Authentication` 外してみたら `Internal Server Error` が帰ってきた。まじか。コレジャナイ感がある。試しに V1 で同じことをしてみたらちゃんと 401 が返ってくるので、 V2 まだ未完成なんかなって察する。

```powershell
Invoke-RestMethod -Method Get -Uri "https://api.ouraring.com/v2/usercollection/personal_info"
# Invoke-RestMethod: Internal Server Error
$res = Invoke-WebRequest -Method Get -Uri "https://api.ouraring.com/v2/usercollection/personal_info" -SkipHttpErrorCheck
$res
# StatusCode        : 500
# StatusDescription : InternalServerError
# Content           : Internal Server Error
# ...
Invoke-RestMethod -Method Get -Uri "https://api.ouraring.com/v1/userinfo"
# Invoke-RestMethod: {"status": 401, "title": "Invalid Access Token", "detail": "The access token provided is expired, revoked, malformed, or invalid for other reasons.", "error": "invalid_token", "error_description": "The access token provided is expired, revoked, malformed, or invalid for other reasons."}
```

最後に Readiness, Sleep, Activity あたりのデータを取得してみる。期間なしで。アラフォーの赤裸々なバイタルデータだ。

```powershell
Invoke-RestMethod -Method Get -Uri "https://api.ouraring.com/v2/usercollection/daily_readiness" -Authentication Bearer -Token $Token | ConvertTo-json -Depth 10
# {
#   "data": [
#     {
#       "contributors": {
#         "activity_balance": 84,
#         "body_temperature": 100,
#         "hrv_balance": 87,
#         "previous_day_activity": 81,
#         "previous_night": 96,
#         "recovery_index": 100,
#         "resting_heart_rate": 100,
#         "sleep_balance": 90
#       },
#       "day": "2022-12-24",
#       "score": 91,
#       "temperature_deviation": -0.08,
#       "temperature_trend_deviation": -0.02,
#       "timestamp": "2022-12-24T09:00:00+09:00"
#     },
#     {
#       "contributors": {
#         "activity_balance": 85,
#         "body_temperature": 96,
#         "hrv_balance": 85,
#         "previous_day_activity": 94,
#         "previous_night": 100,
#         "recovery_index": 96,
#         "resting_heart_rate": 76,
#         "sleep_balance": 92
#       },
#       "day": "2022-12-25",
#       "score": 90,
#       "temperature_deviation": 0.09,
#       "temperature_trend_deviation": 0.06,
#       "timestamp": "2022-12-25T09:00:00+09:00"
#     }
#   ],
#   "next_token": null
# }

Invoke-RestMethod -Method Get -Uri "https://api.ouraring.com/v2/usercollection/daily_sleep" -Authentication Bearer -Token $Token | ConvertTo-json -Depth 10
# {
#   "data": [
#     {
#       "contributors": {
#         "deep_sleep": 99,
#         "efficiency": 98,
#         "latency": 75,
#         "rem_sleep": 100,
#         "restfulness": 88,
#         "timing": 76,
#         "total_sleep": 94
#       },
#       "day": "2022-12-24",
#       "score": 91,
#       "timestamp": "2022-12-24T09:00:00+09:00"
#     },
#     {
#       "contributors": {
#         "deep_sleep": 100,
#         "efficiency": 93,
#         "latency": 91,
#         "rem_sleep": 100,
#         "restfulness": 88,
#         "timing": 83,
#         "total_sleep": 96
#       },
#       "day": "2022-12-25",
#       "score": 94,
#       "timestamp": "2022-12-25T09:00:00+09:00"
#     }
#   ],
#   "next_token": null
# }

Invoke-RestMethod -Method Get -Uri "https://api.ouraring.com/v2/usercollection/daily_activity" -Authentication Bearer -Token $Token | ConvertTo-json -Depth 10
# {
#   "data": [
#     {
#       "class_5_min": "1111111111111111111111111111111111111111111112211223333333323233333233333332222222211322222232222222232332322333234222223233223332222322222",
#       "score": 90,
#       "active_calories": 179,
#       "average_met_minutes": 1.46875,
#       "contributors": {
#         "meet_daily_targets": 78,
#         "move_every_hour": 100,
#         "recovery_time": 100,
#         "stay_active": 75,
#         "training_frequency": 100,
#         "training_volume": 97
#       },
#       "equivalent_walking_distance": 3621,
#       "high_activity_met_minutes": 0,
#       "high_activity_time": 0,
#       "inactivity_alerts": 0,
#       "low_activity_met_minutes": 119,
#       "low_activity_time": 10860,
#       "medium_activity_met_minutes": 37,
#       "medium_activity_time": 600,
#       "met": {
#         "interval": 60.0,
#         "items": [
#           1.1,
#           0.9,
#   長すぎ端折る
#           0.9,
#           0.9
#         ],
#         "timestamp": "2022-12-25T04:00:00+09:00"
#       },
#       "meters_to_target": 8000,
#       "non_wear_time": 0,
#       "resting_time": 15900,
#       "sedentary_met_minutes": 5,
#       "sedentary_time": 14340,
#       "steps": 3898,
#       "target_calories": 550,
#       "target_meters": 12000,
#       "total_calories": 1940,
#       "day": "2022-12-25",
#       "timestamp": "2022-12-25T04:00:00+09:00"
#     }
#   ],
#   "next_token": null
# }
```

Readiness, Sleep は 2 日分、 Activity は 1 日分取れた。日時で実行してデータストアに蓄積するような使い方なら、パラメータを指定する必要もなさそう。基本はデータストアに溜め込むようなことしないだろうし、期間指定してページネーションして利用してねってところか。

スコアはアプリ表示そのままだが、一部の小数位を含む数値はアプリで表示する精度以上に持っている様子。
また Activity は結構色々データを持っていて面白そう。 METs で 1 分刻みのデータ持ってるのかーとか、 `class_5_min` に 5 分刻みのサマリを持ってんのかとか。この強烈なデータ表現はアプリ用なんかな？

単純にデータを見てもまだピンとこないので、更なる探索はアプリの利用も熟れてきてからが良いかも知れない。

### おわりに

自分の体調を可視化できるのすごく良い。踊らされやすい人種なだけかも知れんがこうも行動を変容させられると、可能性を感じざるを得ない。実際にその変わった行動が健康に寄与してるかは健康診断とかの結果見なわからんけど。

アプリの継続に関しては、仮に自分ダッシュボードを作り上げれたら案外要らないのか？まだ装着期間が短いこともあり出ないレポートもあるみたいなので、その辺を拝ませてもらってからメンバーシップなしにするとかの判断もありか。
