テレワークを潤すために SORACOM で目薬カウンターを作る

## はじめに
こちらは [SORACOM Advent Calendar 2020](https://qiita.com/advent-calendar/2020/soracom) 13 日目の記事です。昨日は @ozk009 さんの [SORACOMひげボタンを使った追跡装置の実装方法](https://qiita.com/ozk009/items/2e803f58feabeb1df7de)でした！振動を接点入力にしているアイデアがすごいと思いました。ケースも付けられていてとても実用的です。私も SORACOM LTE-M ボタンネタなので、頑張ります。

当記事では、[SORACOM LTE-M Button for Enterprise (白ボタン)](https://soracom.jp/products/gadgets/enterprise_button/) を使って、勤務中に目薬を点した回数をカウント・可視化しようと思います。また、工夫したポイントとしてリセット機能を付けています。

## 作成の背景

在宅勤務中は時間を忘れて仕事に没頭しがちで、冬の乾燥も相まっていつの間にか目がカラカラになることがよくありました。しっかりと目薬を点して、潤いのあるテレワークにしたいと思ったことが作成のきっかけです。

## 出来上がったもの

[SORACOM Lagoon](https://soracom.jp/services/lagoon/) で可視化しています。iPad などでも Safari で開いてホーム画面に保存すれば簡単に見られます。白ボタンをシングルクリック (またはロングクリック) すると、カウントアップします。また、白ボタンをダブルクリックすると、カウントをリセットします。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">カウントリセットの様子 <a href="https://t.co/ucjLEXWwWb">pic.twitter.com/ucjLEXWwWb</a></p>&mdash; Naoki Mikuni (Mick) (@n_mikuni) <a href="https://twitter.com/n_mikuni/status/1337882061323956226?ref_src=twsrc%5Etfw">December 12, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

## 構成図

全体構成を図示すると以下のようになります。

![overview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125247/b3b87d35-0d6c-e136-df64-ff932fd88725.png)

## 構成の解説

白ボタンでは、過去に何回クリックされたかをハードウェア側で記憶していません。そのため、カウンターを作るにはどこかに「何回クリックされたか」を記録しておく必要があります。記録する場所はいくつか考えられます[^1]が、今回は白ボタンが内蔵しているチップ型 SIM について SORACOM 上で保持している[タグ](https://dev.soracom.io/jp/air_cellular/basic-operation/#tag)に記録することにしました。

[SORACOM Orbit](https://soracom.jp/services/orbit/) にてタグ情報を取得し、クリックタイプに応じてカウントアップまたはリセットし、カウント情報を生成します[^2]。生成したカウント情報は [SORACOM Harvest Data](https://soracom.jp/services/harvest/), [SORACOM Funk](https://soracom.jp/services/funk/) へ送ります。

![orbit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125247/fde01ea9-19a8-f9f3-e018-f6cd3bcf5a17.png)

SORACOM Harvest Data で格納されたデータは SORACOM Lagoon で可視化します[^3]。

![harvest_lagoon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125247/3ef6e4f8-b956-6cca-5d3b-d8f45919ca78.png)

SORACOM Funk では AWS Lambda に連携し、カウント情報を [SORACOM API](https://dev.soracom.io/jp/docs/api_guide/) 経由で SIM のタグに記録します[^4]。

![funk.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125247/6fcae71e-b2e5-b592-7d2c-d9d494d26efe.png)

## 実装詳細 - タグ

以下の SORACOM ユーザーコンソールのキャプチャのように `{"count":number}`のタグを設定しています。最初だけ手動で設定する必要があることに注意して下さい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125247/fcafed18-98b9-c43b-b67e-3e7bac1ad0b7.png)

## 実装詳細 - SORACOM Orbit

SORACOM Orbit は 2020 年 12 月時点で AssembyScript, Rust, C/C++, TinyGo 向けの SDK が提供されています。今回は AssemblyScript にチャレンジしてみました。実装の詳細はコメントに記載しています。decoder や encoder の使い方や型変換はかなり手こずったので、気になることなどあれば記事へのコメントまたは Twitter ([@n_mikuni](https://twitter.com/n_mikuni)) で言ってください。

(実際はエラー処理なども少しありますが、簡略化して要点のみ絞ったコードを紹介します)

```typescript
import {
  getInputBuffer,
  getTagValue,
  setOutputJSON,
} from "orbit-sdk-assemblyscript";
import { JSONDecoder, JSONHandler } from "./lib/decoder";
import { JSONEncoder } from "./lib/encoder";

// デバイスからのデータを演算して SORACOM へ送る関数
export function uplink(): i32 {

  // デバイスからのデータを変数 data に入れる
  // 別途後述する Data クラスの定義が必要なので注意
  const data = new Data();
  const decoder = new JSONDecoder<Data>(data);
  decoder.deserialize(getInputBuffer());

  // count というキーのタグの値を取得する
  const tagcount = getTagValue("count");

  // ダブルクリックならカウントをリセットし、
  // それ以外ならカウントアップして変数に格納する
  let count: f64;

  if (data.clickType == 2) {
    count = 0;
  } 
  else {
    count = parseInt(tagcount) + 1;
  }

  // デバイスからのデータに含まれるクリックタイプやタグの値、
  // それらを演算して生成したカウント情報を SORACOM に送る
  const encoder = new JSONEncoder();
  encoder.setInteger("clickType", data.clickType);
  encoder.setString("lastCount", tagcount);
  encoder.setFloat("newCount", count);
  setOutputJSON(encoder.toString());

  return 0;
}

class Data extends JSONHandler {
  // デバイスから送られるデータ
  public clickType: i64;
  
  // 定義されたパラメータについてデバイスから受け取る
  // 実際は他にも送られているが、ここで定義したものだけ受け取れる
  setInteger(name: string, value: i64): void {
    if (name == "clickType") {
      this.clickType = value;
    }
  }
}

```

## 実装詳細 - SORACOM Lagoon

このようなダッシュボードを作りました。目薬によっても異なると思いますが、私の使っている目薬では 1 日 5 ~ 6 回点すと良いようなので、5 回超えるとみずみずしい青色が表示されるようにしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125247/7cdaeda9-410b-cb1f-aa95-ff236777cb29.png)

1 回の時は砂漠のような茶色です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125247/58811840-67c7-4dd8-0415-71f6865d13d2.png)

左上の[シングルスタットパネル](https://dev.soracom.io/jp/start/lagoon-panel/#singlestat_panel)はこのようにかっこ良い可視化を比較的簡単に作れるので良ければ試してみてください。設定方法の例は [GPS マルチユニットサンプルダッシュボードのドキュメント](https://dev.soracom.io/jp/start/gps_multiunit_lagoon/#panels)が参考になるかと思います。

また、左下の[グラフパネル](https://dev.soracom.io/jp/start/lagoon-panel/#graph_panel)では過去 2 時間点眼がなかった場合にアラートを送ってくれるようにもしています。アラートの設定方法は[公式ドキュメント](https://dev.soracom.io/jp/start/lagoon-alert)を参考にしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125247/dda3e744-d383-0c50-c01f-03d28c62fbcf.png)

送信先をメールに指定して、設定したメッセージを送ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125247/f5f11761-a6bb-9984-3a88-d00797f96ced.png)

## 実装詳細 - SORACOM Funk

SORACOM Funk から AWS Lambda を利用する方法は[公式ドキュメント](https://dev.soracom.io/jp/start/funk_aws_lambda)を参照して下さい。
AWS Lambda では SORACOM API を実行してカウント情報をタグに記録しています。SORACOM API を直接呼び出しても良いのですが、今回は [SORACOM CLI](https://dev.soracom.io/jp/docs/cli_guide/) の [AWS Lambda Layer](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-layers.html) を利用してみました。
SORACOM CLI はローカル環境用のパッケージだけでなく AWS Lambda Layer としても公開されているため、デプロイパッケージに含めることなく利用できます。[node.js で利用する例は SORACOM 公式ブログで紹介されています](https://blog.soracom.com/ja-jp/2020/01/20/notify-slack-of-soracom-usage/)が、今回は Python で実装してみました。実行にはおよそ 5 秒かかりましたので、Lambda のタイムアウト時間はデフォルトよりも長めにしておくことに注意してください。

```python
import json
import os
import subprocess


def lambda_handler(event, context):
    print(json.dumps(event))

    print(json.dumps(context.client_context.custom))
    custom_context = context.client_context.custom

    imsi = custom_context.get('imsi')
    new_count = str(event.get('newCount'))
    
    put_subscriber_tags_cmd = 'soracom subscribers put-tags --imsi ' + imsi \
    + ' --body \'[{"tagName": "count", "tagValue": "' + new_count + '"}]\' '\
    + ' --auth-key-id ' + os.getenv('AUTH_KEY_ID') + ' --auth-key ' + os.getenv('AUTH_KEY')
    
    res = json.loads(subprocess.run(put_subscriber_tags_cmd, shell=True, stdout=subprocess.PIPE).stdout.decode())
    
    print(res)

    return True
```

## おわりに

IoT において「状態」をどこに保管するかはとても悩ましい問題です。このあたりの話は @ma2shita さんの [AWS IoT Events は IoT デバイスの「ステートマシン」](https://qiita.com/ma2shita/items/66ecfdb34cdb16d345d3)という記事をとても参考にさせていただきました。SIM のタグを使うことが良いことなのかはあまり自信がないのですが、一つの実装例として参考になればと思います。また、SORACOM Orbit や SORACOM Funk を使うことで非常にシンプルなデバイスでも幅広い応用ができるようになります。こちらも参考になれば幸いです。

明日は @1stship さんの記事です！とても楽しみにしていますー！！

[^1]: 後述するように Amazon Dynamo DB のような SORACOM 以外のクラウド上に記録することや、同じ SORACOM でもタグではなくメタデータの[ユーザーデータ](https://dev.soracom.io/jp/start/metadata/#read_userdata)に記録することも考えられました。

[^2]: カウント情報を SIM のタグに記録した理由が、SORACOM Orbit を利用していることにあります。SORACOM Orbit ではデバイスから送信されたデータや SIM に設定されたタグ、SIM に所属するグループに設定されたユーザーデータなどを取得し、演算したうえで各種 SORACOM サービスへ連携します。2020 年 12 月時点では SORACOM Orbit はたとえば HTTP クライアントとして Amazon Dynamo DB などに保管されたデータを取得するようなことができないため、SIM のタグに記録しています。

[^3]: SORACOM Orbit を利用している理由が、SORACOM Lagoon で可視化したいことにあります。可視化を外部でやるのであれば、SORACOM Funk で連携した AWS Lambda にてカウント情報を生成し Amazon Dynamo DB などに記録することも考えられます。しかし手軽に可視化できる SORACOM Lagoon を使いたいことから、処理を SORACOM Orbit に寄せています。

[^4]: この実装はやや悩ましいのですが、2020 年時点では SORACOM Orbit はタグの取得はできるものの更新はできないため、SORACOM Funk を使ってタグを更新しています。