LoginSignup
0

More than 1 year has passed since last update.

posted at

updated at

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

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


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

もともと、SORACOM LTE-M Button Plusと磁気式リードスイッチは持っていたのですが、
公式なw組み合わせて、
【IoT DIY レシピ】IoT 体験キット ~磁気センサー~ で作る「ドアの開閉モニタリング」
でやってみることに。

このレシピでは、開閉検知時に、メールで通知するようになっておりますが、
「SORACOM Hosted Funk」という新サービス?を使ってメールを飛ばします。
9. SORACOM Funk でメールによる通知をする

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

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

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

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

前置き長っ!


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

構成

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

送信情報格納

まず、送信先のメールアドレスですが、
ボタンごとに変えたいとかそういうこともある(きっとある)と思うので、
メールアドレスはSIMのタグにセットします。

メールの件名と本文は、SIMグループのユーザーデータにセットします。
JSON形式(今回初めて使ったw)にしてます。
スクリーンショット 2020-08-19 10.58.20.png

あと、Orbitのこの設定を忘れずにですかね。
スクリーンショット 2020-08-19 11.09.12.png

Orbit側

前回のソースを変更してます。

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

#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に投げるだけです。

'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を実行権限ついているぐらいでしょうか。。。

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

届いたメールは一旦こんな感じ。

まあ、そのままですね。

まとめ

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


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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0