SORACOM Orbit試してみた 〜Hosted Funkもどきを実装する〜

SORACOM Orbit試してみた3回目です。(時間が経ってしまい申し訳ないです。)

---

さて、ソラコムさんの年次のイベント、SORACOM Discovery Online 2020のナイトイベント内であった
ソラコムウルトラクイズで、大人気なく全力で答えてしまい、
去年に引き続き、景品(「IoT 体験キット ～磁気センサー～」)をゲットしてしまいました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ソラコムウルトラクイズの景品届いたー！<br>せっかくなので、Let&#39;s try IoT プロトタイピングオンラインのドア開閉モニタリングのやつ、やってみよう(一応そろえてたので、やったけど)。<br>Hosted Funkの部分は、Orbit使って自前のFunk &amp; Lambdaで！<a href="https://twitter.com/hashtag/soracom?src=hash&amp;ref_src=twsrc%5Etfw">#soracom</a> <a href="https://t.co/zEgditWjYe">https://t.co/zEgditWjYe</a></p>&mdash; Kenichiro Wada (@Keni_W) <a href="https://twitter.com/Keni_W/status/1284342769989890048?ref_src=twsrc%5Etfw">July 18, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

もともと、SORACOM LTE-M Button Plusと磁気式リードスイッチは持っていたのですが、
公式なw組み合わせて、
[【IoT DIY レシピ】IoT 体験キット ～磁気センサー～ で作る「ドアの開閉モニタリング」](https://soracom.github.io/iot-recipes/door-monitoring-by-iot-starter-kit-magnetic-sensor/)
でやってみることに。

このレシピでは、開閉検知時に、メールで通知するようになっておりますが、
「SORACOM Hosted Funk」という新サービス？を使ってメールを飛ばします。
[9. SORACOM Funk でメールによる通知をする](https://soracom.github.io/iot-recipes/door-monitoring-by-iot-starter-kit-magnetic-sensor/#8)

無論、レシピでは、自前のLambdaから飛ばすこともでき、その案内もありますが。。。
[前回](https://qiita.com/keni_w/items/2f8ec1d6309cd9c5c4ff)も記載しましたが、
タグ情報やユーザーデータの情報を取得するためには、Lambda側で以下の実装が必要でした。

1. 認証キー(もしくは他の手段)で、トークンを取得
2. トークンをパラメータに、SIM情報を取得

そこに登場したのが、SORACOM Orbitです。
Orbitを使えば、この辺が一切不要になります！(再掲)

これは作ってみないとってことで、
今回は、SORACOM Orbitでタグにセットしたメールアドレスと、ユーザーデータの情報を取得し、Funkを使って、AWS Lambdaに渡して、Lambdaからメールを飛ばすというのをやってみました。

前置き長っ！

---

ここからはちゃっちゃと。

### 構成
<img src="https://docs.google.com/drawings/d/e/2PACX-1vSk4Duh3Lx0TZD4VPglGVqWQQ9GO_QEftnfuw0vA8ye7zPlvdff4jj9h1Or1BsnWMCCT3dbHVlKdD4N/pub?w=1221&amp;h=477">

メールはSES(最近来たTokyoリージョン使ってたりします)で飛ばしてます。
個人＆小規模の場合は、メールアドレスの認証を忘れずに。。。

### 送信情報格納
まず、送信先のメールアドレスですが、
ボタンごとに変えたいとかそういうこともある（きっとある）と思うので、
メールアドレスはSIMのタグにセットします。
<img src="https://docs.google.com/drawings/d/e/2PACX-1vSOMx1ISGjDNsDGLsbFOvEbOn8_KavE1ADeoIuav8hUG0X7p0h97NDBD20Grx5NsbpsJzhWb6anOcYv/pub?w=967&amp;h=542">

メールの件名と本文は、SIMグループのユーザーデータにセットします。
JSON形式（今回初めて使ったw）にしてます。
![スクリーンショット 2020-08-19 10.58.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72752/15948f2e-7808-de80-4765-8e68882011c2.png)

あと、Orbitのこの設定を忘れずにですかね。
![スクリーンショット 2020-08-19 11.09.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72752/e458448e-5083-f6f9-35d8-b049dbeb671d.png)


### Orbit側
[前回](https://qiita.com/keni_w/items/2f8ec1d6309cd9c5c4ff)のソースを変更してます。

変更点としては、TagからEmailの情報を取得したぐらいでしょうか。

```c++
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <string>

#include <emscripten.h>
#include "soracom/orbit.h"

#include "nlohmann/json.hpp"
using nlohmann::json;

int32_t uplink_body();

extern "C" {

EMSCRIPTEN_KEEPALIVE
int32_t uplink() {
    soracom_log("hello, orbit! for IoT Button Edition.\n");

    return uplink_body();
}

}

std::string get_tag(const std::string& tag_name) {
    const char* tag_value = NULL;
    size_t tag_value_len = 0;
    int32_t err = soracom_get_tag_value(tag_name.c_str(), tag_name.size(), &tag_value, &tag_value_len);
    if (err < 0) {
        return "";
    }

    return std::string(tag_value);
}

std::string get_source_item(const std::string& name) {
    const char* value = NULL;
    size_t value_len = 0;
    int32_t err = soracom_get_source_value(name.c_str(), name.size(), &value, &value_len);
    if (err < 0) {
        return "";
    }

    return std::string(value);
}

double deg2rad(double deg) {
    return deg * (M_PI / 180);
}

double calc_distance_in_km(double lat1, double lon1, double lat2, double lon2) {
    double r = 6371; // Radius of the earth in km
	double d_lat = deg2rad(lat2 - lat1);
	double d_lon = deg2rad(lon2 - lon1);
	double a = sin(d_lat/2)*sin(d_lat/2) + cos(deg2rad(lat1))*cos(deg2rad(lat2))*sin(d_lon/2)*sin(d_lon/2);

	double c = 2 * atan2(sqrt(a), sqrt(1-a));
	return  r * c;
}

double parse_double(const std::string& s, double default_value) {
    char *p_end;
    double d = strtod(s.c_str(), &p_end);
    if (*p_end != NULL) {
        return default_value;
    }
    return d;
}

int32_t uplink_body() {
    const char* buf = NULL;
    size_t siz = 0;
    int32_t err = soracom_get_input_buffer_as_string(&buf, &siz);
    if (err < 0) {
        return err;
    }

    soracom_log("received data: %s\n", buf);

    json j = json::parse(buf);
    soracom_release_input_buffer(buf);

    std::string imsi = get_source_item("resourceId");

    soracom_log("imsi = %s\n", imsi.c_str());
    std::string name = get_tag("name");
    std::string center_lat_str = get_tag("center_lat");
    std::string center_lon_str = get_tag("center_lon");
    std::string radius_str = get_tag("radius");

    std::string employee_code = get_tag("employee_code");
    std::string email = get_tag("email");

    j["name"] = name;
    j["email"] = email;
    j["employee_code"] = employee_code;
    j["imsi"] = imsi;
    j["inside_area"] = 0;

    const char* userdata = NULL;
    size_t ud_siz = 0;
    err = soracom_get_userdata_as_string(&userdata, &ud_siz);
    if (err < 0) {
        return err;
    }
    soracom_log("userdata: %s\n", userdata);
    j["user_data"] = std::string(userdata);
    soracom_release_userdata(userdata);

    std::string output = j.dump();
    soracom_set_json_output(output.c_str(), output.size());

    return 0;
}
```

### Lambda側
今回新たに作成しました。
eventオブジェクトから、メールアドレスと、メールの件名、本文を取得し、SESに投げるだけです。

```js
'use strict';
const AWSXRay = require('aws-xray-sdk')
const AWS = AWSXRay.captureAWS(require('aws-sdk'));
AWS.config.update({ region: 'ap-northeast-1' });
const ses = new AWS.SES

async function sendMail(userData, email) {
  let mailAddressList = email.split(',');;
  let mailSubject;
  let mailBody;
  let isError = false;
  if (userData['subject']) {
    mailSubject = userData['subject'];
  } else {
    console.log('Not Setting subject')
    isError = true;
  }
  if (userData['body']) {
    mailBody = userData['body'];
  } else {
    console.log('Not Setting body')
    isError = true;
  }
  if (!isError) {
    const params = {
      Source : 'xxxxxxxx@gmail.com',
      Destination : {
        ToAddresses : mailAddressList
      },
      Message : {
        Subject : {
          Data : mailSubject ,
          Charset : 'utf-8'
        },
        Body: {
          Text: {
            Charset: 'utf-8',
            Data: mailBody
           }
        }
      }
    }

    try {
      const res = await ses.sendEmail(params).promise();
      console.log(res)
      if (res.data === 'ok') {
        return true;
      } else {
        return false;
      }
    } catch (error) {
      console.log('SES SendEmail Error: ' + error);
      return false;
    }

  } else {
    return false;
  }

}

module.exports.index = async(event, context)  => {
  console.log(JSON.stringify(event, 2));
  console.log(JSON.stringify(context, 2));

  let isError = false;
  if (!event.email) {
    console.log('Send Email Address Not Setting.')
    isError = true;
  }
  if (!isError) {
    if (event.user_data) {
      if (await sendMail(JSON.parse(event.user_data), event.email) == false) {
        isError = true;
      }
    } else {
      console.log('userdata Not Setting.')
      isError = true;
    }
  }


  if (isError) {
    return {
      statusCode: 500,
      body: JSON.stringify(
        {
          message: 'userdata Not Setting. Not Sending Mail.',
          input: event,
        },
        null,
        2
      ),
    };
  } else {
    return {
      statusCode: 200,
      body: JSON.stringify(
        {
          message: 'Go Serverless v1.0! Your function executed successfully!',
          input: event,
        },
        null,
        2
      ),
    };
  }

};
```


Serverless Frameworkを使っているので、
serverless.yamlも一緒に。
SESを実行権限ついているぐらいでしょうか。。。

```yaml
service: soracom-iot-button-mail-sender

provider:
  name: aws
  runtime: nodejs12.x
  stage: dev
  region: ap-northeast-1
  iamRoleStatements:
    - Effect: Allow
      Action:
        - ses:SendEmail
        - ses:SendRawEmail
      Resource: '*'
    - Effect: Allow
      Action:
        - xray:PutTraceSegments
        - xray:PutTelemetryRecords
      Resource: "*"
plugins:
  - serverless-plugin-tracing
functions:
  sendMail:
    handler: handler.index
    memorySize: 128
    timeout: 10
    tracing: PassThrough
```

届いたメールは一旦こんな感じ。
<img src="https://docs.google.com/drawings/d/e/2PACX-1vRbNeldSxufZDduVRdGxyYK84ENuF9CQyxlTGccbSmYpYcvKMox3UN-93P4jncoV2R5kKqFvMuV4Gnt/pub?w=473&amp;h=178">

まあ、そのままですね。

### まとめ
前述した通り、
Orbitなしで実装する場合は、
メールアドレスの情報、メール本文等は、
Lambdaでタグの情報、ユーザーデータの情報をSORACOM APIを使って、取得する。
もしくは、メールアドレスはDynamoDBなどで管理する必要がありました。
Orbitを使うことで、
情報をSORACOMプラットフォーム上で一元管理できるので、
構成が非常にシンプルになりました、

---

次で一旦試してみた系はラストの予定です。