Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
1
Help us understand the problem. What is going on with this article?
@keni_w

SORACOM Orbit試してみた 〜SORACOM LTE-M Button for Enterprise編〜

続編です。(時間が経ってしまい申し訳ないです。)


前回、SORACOM Orbit試してみた 〜SORACOM GPS マルチユニット編〜では、
サンプルを使って、一通り動作確認できました。

次は、
C++のサンプルを直す形で、
SORACOM LTE-M Button for Enterprise
で使えるようにしてみたい思います。

ちなみに、RustにはSORACOM LTE-M Button for Enterprise対応のサンプルがあります。

どうなったか。

Orbit登場までも、Lambda側で、SORACOM APIもしくはSORACOM CLIのLambda Layerを使って、SIMの名前やタグなどの情報を取得することができました。
その際は、以下のような実装を行う必要がありました。
1. 認証キー(もしくは他の手段)で、トークンを取得
2. トークンをパラメータに、SIM情報を取得

しかし、Orbit側で、名前やタグにセットされた値を取得することができるので、
この処理が不要になりました。

  • Orbit登場前
    スクリーンショット 2020-08-18 10.20.47.png

  • Orbit登場後
    処理流れ(Orbit登場後)

となりました。

Lambda側のコード量としては、約半分(SORACOM LTE-M Button powered by AWS用の実装もあったり、いろいろ変えたのもありますが)になり、環境変数にKMSで暗号化した認証キーを保存していましたが、それも不要になりました。

処理が減ったことで、初回起動時間としては、以下のようになっています。

  • Orbit登場前 Lambdaの実行時間等(Orbit登場前)
  • Orbit登場後 Lambdaの実行時間等(Orbit登場後)

以前、Lambdaの呼び出しおよび実行に成功しているのに、ボタンが赤点滅することあったのですが、
初回起動時などで、2秒ぐらい掛かっているときに起きることがありました。
聞いたところは、認証で時間かかると。Funk側がタイムアウトしてしまっているのではということでしたが、
Lambda側の処理がスッキリしたので、今後はこういうこともなさそうです。

ソース

Orbit側

ベースはSORACOM GPSマルチユニットのものです。

#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");

    j["name"] = name;
    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側

ベースは
SORACOM Funkを使ってみた。
で作成したものです。

'use strict';
const moment = require('moment-timezone');
const AWSXRay = require('aws-xray-sdk')
const AWS = AWSXRay.captureAWS(require('aws-sdk'));
AWS.config.update({ region: 'ap-northeast-1' });
const kms = new AWS.KMS();
AWSXRay.captureHTTPsGlobal(require('https'))
AWSXRay.capturePromise();
const axios = require('axios');

const encryptedSlackWebHookUrl = process.env['SLACK_WEBHOOK_URL'];
let decryptedSlackWebHookUrl;

const clickTypeNameJp = {
  1: 'クリック',
  2: 'ダブルクリック',
  3:'長押し'
}

/**
 * 環境変数復号化
 * @param {*} decryptedEnvKey 
 */
async function decryptedEnv(decryptedEnvKey) {
  const data = await kms
    .decrypt({
      CiphertextBlob: Buffer.from(process.env[decryptedEnvKey], 'base64'),
    })
    .promise();
  return String(data.Plaintext);
}

/**
 * データ取得
 * @param {*} event 
 */
async function getData(event, context) {

  const clickType = event.clickType;
  const clickTypeName = clickTypeNameJp[`${clickType}`];
  const name = event.name;
  const batteryLevel = event.batteryLevel;
  const locationQueryResult = context.clientContext.custom.locationQueryResult;
  let locationLat = null;
  let locationLon = null;
  if (locationQueryResult == 'success') {
    locationLat = context.clientContext.custom.location.lat;
    locationLon = context.clientContext.custom.location.lon;
  }

  return {
    clickType,
    clickTypeName,
    name,
    batteryLevel,
    locationQueryResult,
    locationLat,
    locationLon,
  };
}

/**
 * Slack POST
 * @param  {[type]} data [description]
 * @param  {[type]} isError [description]
 * @return {[type]}       [description]
 */
async function postSlackMessage(data) {

  console.log('slack Call.' + JSON.stringify(data));

  let messageArray = [];
  messageArray.push(`ボタン ${data.name}${data.clickTypeName}されました。`);
  let battryLevelInfo = '電池残量は十分です。'
  if (data.batteryLevel < 0.3) {
    battryLevelInfo = 'そろそろ電池交換の時期です。'
  }
  const batteryLevelPercent = data.batteryLevel * 100;
  messageArray.push(`バッテリー残量は${batteryLevelPercent}です。${battryLevelInfo}`);

  // 位置情報付与
  if (data.locationQueryResult === 'success') {
    const mapUrl = 'https://www.google.com/maps?q=,';
    messageArray.push('ボタンの位置は');
    messageArray.push(`https://www.google.com/maps?q=${data.locationLat},${data.locationLon}`);
  }

  const message = messageArray.join('\n');
  // リクエスト設定
  const payload = {
    text : message
  };
  // メッセージ送信
  try {
    const res = await axios.post(decryptedSlackWebHookUrl, JSON.stringify(payload));
    if (res.data === 'ok') {
      return true;
    } else {
      return false;
    }
  } catch (error) {
    console.log('Slack Post Error: ' + error);
    return false;
  }
};

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

  decryptedSlackWebHookUrl = await decryptedEnv('SLACK_WEBHOOK_URL');
  const data = await getData(event, context);

  let isError = false;
  if (!await postSlackMessage(data)) {
    isError = true;
  }

  if (!isError) {
    return {
      statusCode: 200,
      body: JSON.stringify(
        {
          message: 'All Action Successful!',
          input: data,
        },
        null,
        2
      ),
    };
  } else {
    return {
      statusCode: 500,
      body: JSON.stringify(
        {
          message: 'Action Failed!',
          input: data,
        },
        null,
        2
      ),
    };
  }
};

まとめ

今回のサンプルでは、タグから名前の他にemployee_code(社員番号)を取得して、Lambda側に渡してます(今の所、渡しただけで使ってないんですw)、
ボタン=ユーザーというケースに、その紐付けを管理するためには、別で管理(DynamoDBに格納とか)する、APIで取得するなど、しないといけなかったと思いますが、その辺が楽になったのではないでしょうか。
どこまでタグに格納して良いんだ?っていう話にもなりそうですが。。。

次回予告

SORACOM Discovery 2020 Onlineのナイトイベントで行われたソラコム ウルトラクイズで、
大人気なく、全力で答えてしまい、「IoT 体験キット ~磁気センサー~」をゲットしたので、
改めて、【IoT DIY レシピ】IoT 体験キット ~磁気センサー~ で作る「ドアの開閉モニタリング」をやってみます。
そして、レシピで出てくるHosted Funkと同等の仕組みをOrbitとLambdaで実装したいと思います。

1
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
keni_w
ガンダムと銀河英雄伝説を養分に生きるアラフォーエンジニア。 なお、こちらへの掲載内容は私自身の見解であり、会社の立場、戦略、意見を代表するものではありません。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
1
Help us understand the problem. What is going on with this article?