LoginSignup
2
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2020-08-24

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プラットフォーム上で一元管理できるので、
構成が非常にシンプルになりました、


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

2
0
0

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
  3. You can use dark theme
What you can do with signing up
2
0