fitbitとAlexaスキルを連携させてみる

# はじめに
最近[fitbit versa3](https://www.fitbit.com/jp/versa3)を買いました。心拍数や睡眠状態など色々見れて面白いです。


このモデルにはAlexaが搭載されており、音声でスキル呼び出しが可能です。
[fitbitの公式Alexaスキル](https://www.amazon.co.jp/Fitbit-Inc/dp/B01CH4BP28)はすでにありますが、自分でもスマートウォッチで集めた情報を利用したスキルを作れるのか、試してみようと思います。

# スキル開発
## 概要
スマートウォッチから直接情報を得るわけではなく、fitbitのサイトからAPI経由で取得します。だいたい以下のようなイメージですね。
![全体図7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114715/3e18a656-4f7c-d7a6-9a85-d54124ac43ae.png)
Alexaの[アカウントリンク](https://developer.amazon.com/ja-JP/docs/alexa/account-linking/understand-account-linking.html)機能を使って、Alexaとfitbit間でID連携する形になります。

## fitbit側
### アプリケーション登録
[fitbitのdeveloperサイト](https://dev.fitbit.com/)から、Regiter An Appでアプリケーションを新規登録します。
特に開発者アカウント登録などしなくても、通常のfitbitアカウントで利用できるようです。
![fitbit1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114715/112fd52a-f695-26ad-322d-9744fa66a18b.png)
以下のようにアプリケーション設定を登録します。

| 項目名 | 登録内容 |
|:--|:--|
|Application Name   |任意のアプリケーション名   |
|Description   |アプリケーションの説明   |
|Application Website   |アプリケーションのWebサイト。Alexaスキルが公開されたらamazon.co.jpの該当スキルの画面を入れるのがよさそうです   |
|Organization   |自身の組織名   |
|Organization Website   |組織のWebサイト   |
|Terms Of Service Url   |利用規約のURL   |
|Privacy Policy Url   |プライバシーポリシーのURL   |
|OAuth 2.0 Application Type   |Client   |
|Callback URL   |※後述|
|Default Access Type   |Read-Only   |
試験的に動かすだけであれば、OAuth 2.0 Application Type、Callback URL、Default Access Type以外はダミー値で大丈夫です。ただし、利用者に提示される内容なので、実際に公開する場合はきちんとした値を入れる必要があります。

Callback URLだけは要注意です。
fitbit側で整合性チェックをしているようで、Alexa側から渡ってくるコールバックURLと一致した値を入れておかないと、後で利用者向けの同意画面を表示するときにエラーになります。
この時点ではとりあえず何かURLを入れておき、後続の作業でAlexa側のコールバックURLが判明したら書き換えます。

登録できると、以下のように各種IDや連携に必要なURLが表示されます。
ここの値は次の工程で使います。
![fitbit3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114715/9847620d-25af-9e16-574c-b54884379008.png)

## Alexaスキル側
### アカウントリンク設定
[Alexa Developer Console](https://developer.amazon.com/alexa/console/ask)でアカウントリンク設定をしていきます。
![alexa3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114715/ecf79979-1640-4d60-8d52-ea9bcc9c5952.png)

設定内容は以下です。

| 項目名 | 登録内容 |
|:--|:--|
|Authorization Grant種別   |Auth Code Grant|
|Web認証画面のURI   |fitbit側の「OAuth 2.0: Authorization URI」|
|アクセストークンのURI   |fitbit側の「OAuth 2.0: Access/Refresh Token Request URI」|
|ユーザーのクライアントID   |fitbit側の「OAuth 2.0 Client ID」|
|ユーザーのシークレット   |fitbit側の「Client Secret」|
|ユーザーの認可スキーム   |HTTP Basic認証|
|スコープ   |※後述   |
|ドメインリスト   |空でOK   |
|デフォルトのアクセストークンの有効期限   |空でOK|
スコープには、[こちら](https://dev.fitbit.com/build/reference/web-api/oauth2/#scope)を参照してアクセス許可を与える対象を指定します。
今回は心拍数の情報を使いたいので、「heartrate」を指定します。

また、このタイミングでAlexaのリダイレクト先のURLがわかりますが、
このURLと同じ値を全て、fitbit側設定の「Callback URL」に反映させておきます。
![alexa2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114715/c4ae9ef5-a42a-466b-3f8e-c24cda61d5e0.png)

### 実装
ここまでの設定で、利用者側でアカウントリンク設定が済んでいれば、fitbitのAPIを呼び出すためのアクセストークンが自動的にLambdaまで渡ってくるようになります。

``` リクエスト情報
{
    "requestEnvelope": {
        "version": "1.0",
        "session": {
            "new": true,
            "sessionId": "amzn1.echo-api.session.xxx...",
            "application": {
                "applicationId": "amzn1.ask.skill.xxx..."
            },
            "user": {
                "userId": "amzn1.ask.account.xxx...",
                "accessToken": "xxx..."    // ←☆これ
            }
        },
```

ソース内ではアクセストークンを取得し、[API仕様](https://dev.fitbit.com/build/reference/web-api/)や[Swagger](https://dev.fitbit.com/build/reference/web-api/explore/#/Body%20and%20Weight/addWeightLog)を確認しながら必要なAPIを呼んであげればOKです。ソース全体は[こちら](https://github.com/uemuram/alexa-fitbitTest)です。

``` index.js
const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
    },
    async handle(handlerInput) {
        // アクセストークンを取得
        const token = Alexa.getAccountLinkingAccessToken(handlerInput.requestEnvelope);
  
        // 情報取得
        const response = await Axios.get(
            'https://api.fitbit.com/1/user/-/activities/heart/date/today/1d.json',
            { headers: { Authorization: `Bearer ${token}` } }
        );
        const restingHeartRate = response.data['activities-heart'][0].value.restingHeartRate;

        return handlerInput.responseBuilder
            .speak(`今日の安静時の心拍数は${restingHeartRate}です。`)
            .getResponse();
    }
};
```

今回は、心拍数を取得する[heart-rate API](https://dev.fitbit.com/build/reference/web-api/heart-rate/)を呼び出し、そこから安静時の心拍数(restingHeartRate)を取り出しています。
また、このソースでは省略していますが、実際にはトークンをとれなかったときに連携設定を促す処理などが別途必要になります。[こちら](https://developer.amazon.com/ja-JP/docs/alexa/account-linking/add-account-linking-logic-custom-skill.html)などを参考に実装するのがよいでしょう。


# 利用者から見た動き
## アカウントリンク
スキルを有効にした後、Alexaアプリからアカウントリンクを行います。
![連携設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114715/11f2804d-956d-870b-f405-d0aa63c640d7.png)
fitbitにログインしていなければログインを求められ、その後心拍数データ取得の同意を確認する画面フローになります。

## スキル呼び出し
fitbitに向かってスキル起動を呼びかけてみます。
![result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114715/3096077c-d8f8-f70e-8bcd-1cb5dfa58258.png)
出た！やった！
画面表示だけでなく、きちんと読み上げてくれます。

# おわりに
アカウントリンク機能を使って、fitbitとAlexaスキルを連携させることができました。
利用者ごとのトークン管理やリフレッシュなどの面倒なところをAlexaが全部やってくれるので、思っていたより遥かに簡単でした。

心拍数を表示するだけであれば標準機能でも普通にできるので、あえてAlexaスキルを使う強みがあるとしたら、音声が使える点や、他の据え置きのAmazon Echoなどからも同じように呼べる点になるかと思います。
どんなスキルを作れるかはアイデア次第ですね。