初心者がどハマりしたAWS IoTのRuleの概要と使い方まとめ

# AWS IoT使ってますか！

Webコンソールが分かりづらかったり新UIになったりで、
まだまだ黎明期感を感じるAWS IoTですが、最近ようやく僕もはじめました。
ただ毎回何かやる度にハマって学んでを繰り返してる感じで、
毎日ぐぬぬってなってます(でもこういう感覚っていいですよね！)。

で、今回もRuleっていう概念をお勉強してハマったんですが、
せっかくなのでハマったことも含めて概要とか使い方まとめてみました。
僕みたいな誰かの役に立てれば幸いです:sweat_smile:

# まずAWS IoTのRuleとは！

AWS IoT内の指定したTopicに更新があった場合に、
LambdaやSNSなど外部のサービスをキックすることができるAWS IoTの重要なサービス！

例えば予め指定したデバイスShadowのTopic更新を受けて、
それを元にLambdaでクラウド側にデータを保存したり、
Shadowを再更新してあげたりなどいろいろできます。

正直このRuleを上手く使えないとAWS IoTで出来ることが全然広がりません :joy:

# 作ってみよう

と思ってCreateボタンを押してみてもぱっと見何がなんだかわかりません。
この初心者殺しめっ！！:knife:

ので、メモも兼ねて解説していきます。

## Description

まぁただの説明なので省略

## Using SQL version 

一番新しいものを使えばいいと思います。

## Rule query statement

ここに、これより下の欄で設定した結果が表示されます。
最終的には以下のようになりました。 ※可読性の為改行しています。

```sql:RuleQueryStatementの例
SELECT { "payload": *, "topicName": topic() } \
  FROM '$aws/things/+/shadow/update/accepted' \
  WHERE \
    indexof(topic(), '_foo_bar_baz_') > 0
```

細かい説明は下記で。

## の前にまずは心構え

### `*` はペイロード！

SQLでいうところのSELECT句の値となる場所にある `*` ですが、
AWS IoTのRuleだとこれは特別な意味を持っています。

非常に分かり辛いですが、 `*` だけだとTopicのペイロードがそのまま入ってくる感じで、
例えばSELECT句にこの `*` だけが指定されたRuleからLambdaがキックされた場合、
Lambda側で受け取れる `event` オブジェクトの中身はそのままTopicのペイロードと同一になります。

そして後述しますが、このSELECT句にはJSON形式でも記述できたりするので、
ペイロードそのものも、JSON内の1要素として扱うことができます。

### 関数を駆使する

正直 `*` でペイロードだけ取得するのでは若干情報が足りず扱いづらいです。
例えばそのRuleが実行されるにあたって、条件にマッチした複数のTopicから
どのTopicが呼んだものなのかを判別したりをペイロードの内容からだけでは実現できません。

そこでAWSが予め用意している便利な関数群があるので、これを使います。
先の例では `topic()` などがそうですね。これを利用すると、実際に実行されたトピック名を取得できるので、
ここから後でthing名なども抜き出すことができます。

ちなみにこの関数ですが下記にまとまっています。
但し、 **日本語ページにはない情報が結構あります** ので、必ず英語ページから探しましょう(めちゃくちゃハマりました…)。

- [Functions - AWS IoT](http://docs.aws.amazon.com/iot/latest/developerguide/iot-sql-functions.html)

## Attribute

SELECT句の値となる場所です。
上記心構えでも説明しましたが、 `*` だとTopicのペイロードがそのままきます。
JSON形式での記述もでき、下記のような感じで書くことができます。

```json:Attributesの例
{ "payload": *, "topicName": topic() }
```

先ほど利用した関数 `topic()` が使われていますね。
上記で実際にキックされたLambdaで `event` オブジェクトに入ってくる値が下記のようになります。

```javascript:eventオブジェクトの中身
{
  payload: {
    state: {
      reported: { ... },
      desired: { ... },
      delta: { ... },
    }
  },
  topicName: '$aws/things/dev_foo_bar_baz_topic/shadow/update/accepted'
}
```

ペイロードが `payload` , トピック名が `topicName` というキーでラップされてちゃんとすべて渡されてきましたね！ :+1: :sparkles:

## Topic filter

ここで、どのトピックを対象にするのかの基本的な条件をMQTTのEndpointで指定します。
このEndpointは例えばデバイスシャドウなら各Thingの `Interact` -> `MQTT` 内で見つけられます。

```text:TopicFilterの例
$aws/things/+/shadow/update/accepted
```

上記でも利用していますが、MQTTのTopic名には、MQTT専用のワイルドカードが利用できます。
`#` で前方一致、 `+` で部分一致という意味で、組み合わせも可能ですが、
文字列の途中で突然 `+` と使うことはできず `/` の区切りでのみ有効なことに注意しましょう( `hoge+fuga` みたいなのはダメ)。

## Condition

上記TopicFilterだと、すべてのデバイスシャドウの `update/accepted` に対して反応してしまいます。
複数のプロダクトとかを1つのAWSアカウントで作成している時や、
その更新に対して関係ないデバイスに対しても一々反応して欲しくないですよね。
そこでConditionを書くと、ちょうど最初の `Rule query statement` の項に書かれている
SQLの `WHERE` 句として挿入されます。

```text:Conditionの例
indexof(topic(), '_foo_bar_baz_') > 0
```

そしてここでもここでも関数が活躍しています。

`indexof()` は、第一引数で指定した文字列(カラム名ならその値)から、第二引数で指定した文字を検索し、
そのマッチしたIndexを返すというよくあるindexOf関数ですが、これを先ほどの `topic()` と組み合わせることで、
このRuleのトリガーとなったトピック名に `_foo_bar_baz_` の名前が含まれている場合のみ、
Ruleを実行するというようなことが書けるようになります。便利ですね！ :sparkles:

# まとめ

正直これらの機能をフル活用しないとまともなRuleが書けない割に、
意外とまだWebに情報が転がってないので、
辛く厳しいAWSドキュメントの海を彷徨わないとなかなか正解に辿りつけずげんなりします。

というわけで僕は少なくとも最初にハマったのでメモとして残しておきます！

僕らのAWS IoTとの戦いはまだ始まったばかりだ…！！ :collision: :muscle: