Wio LTEで遊んでみる(その8:外部システムへ連携)


Grove IoT スターターキット for SORACOMで遊んでみるの続き。

今回の目的

今まではHarvestへ連携していたデータをBeam,AWS Lambdaを使用して別システムへ連携させることを目的とする。
GPSで位置情報が取得できるのでArcGISへ連携してみる。
※可能であればSlackにも通知したい。

※2018/08/03時点では未完成

必要なものを順番に準備

連携先(ArcGIS)を作成

https://developers.arcgis.com/
まずアカウントを作る。
※既にあるのであれば不要。

情報を流し込むためのレイヤーを作成。

[Details]
名前とタグを適当に設定

[Geometry]
今回は点のデータなのでpointを指定。
測地系(Spatial Reference)はたぶん世界測地系なので4326のままでOK

[Fields]
今回使用する項目を定義する
とりあえずは以下を想定
imei:SIMのIMEI:データ的には数字列なのだが、Longだと桁数的にはいらないので文字列にしておく
time:取得日時用?
lat:緯度
lon:経度
mode:送信モード:モードの設定に対してコード値ドメインも設定
temp:温度
humi:湿度
g_ave:揺れ関係のデータ

other_string:後で追加したくなったとき用(文字列)
other_double:後で追加したくなったとき用(数字)
other_date:後で追加したくなったとき用(日時)

[Settings]
ShareLayer
セキュリティ的なものも考えるとPrivateがいいんだが、外から呼び出す際にトークンを生成したりするのがめんどくさいのでとりあえずPublicにしてしまう。

Permissions Settings
Publicにしている代わりというわけではないが、Addのみにしておく。


で、CreateLayer

作成したレイヤーがちゃんとできているかを確認する。
レイヤーからWebMapを作成。
[編集]を押下して、New Featureで追加できることを確認。

これでArcGIS側の準備はOK

作成したフィーチャレイヤーのServie URLを控えておく。

https://[xxxxxxxx].arcgis.com/[XXXXXXXXXXXX]/arcgis/rest/services/fromsoracom_01/FeatureServer

これの後ろに[/0/applyEdits]をつけ、以下のDataをPOSTしてあげると登録できる。
※この段階で一度試してみておくとよい。

  • URL:
    https://[xxxxxxxx].arcgis.com/[XXXXXXXXXXXX]/arcgis/rest/services/fromsoracom_01/FeatureServer/0/applyEdits

  • Content-Type:application/x-www-form-urlencoded

  • Data : "adds=" + json_q + "&f=pjson"

json_qの中身はこんな感じ。

{
    "geometry":{
        "x":140,
        "y":35,
        "spatialReference":{"latestWkid":4326,"wkid":4326}
    },
    "attributes":{
        "imei":1234,
        "mode":0,
        "temp":30,
        "humi":60,
        "g_ave":2,
        "lon":140,
        "lat":35
    }
}

SORACOM Beam→AWS API Gateway →AWS Lambda

上記で単純なPythonからの登録処理はできそうなのでSORACOM→AWS Lambdaの部分を作る

以下を参考にする。
https://dev.soracom.io/jp/docs/aws_guide/

API Gateway → Lambdaの部分までは問題なく完了。
外部からAPIをCallしてArcGISへ登録できることも確認。

ただ、SORACOM Beam → API Gatewayの部分の連携がよくわからない。
どういう形式で飛んでいくことを想定しているのか。。。
POST/PUTのJsonのBodyがそのまま[$input.json('$')]の部分に当てはまるということなのだろうが
UDP経由でBeamに上げHTTPSで転送した場合どうなるのだろう?

具体的にいうと

int connectId;
connectId = Wio.SocketOpen("beam.soracom.io", 23080, WIOLTE_UDP);
if (!Wio.SocketSend(connectId, data)) 
{
SerialUSB.println("### SocketSend ERROR! ###");
goto err_close;
}

int length;
length = Wio.SocketReceive(connectId, data, sizeof (data), RECEIVE_TIMEOUT);
if (length < 0) 
{
SerialUSB.println("### SocketReceive ERROR! ###");
goto err_close;
}

という流れの中で
### SocketReceive ERROR! ###
となる。

以下を読むとBase64で変換されてしまっているのからなのか?
https://dev.soracom.io/jp/beam/send-data-using-beam/#udp2http
変換したものを送って、それを直接読み込もうとしているからエラーになっているということなのか?

ただ、軽く調査した感じだとAWSのLambdaまでデータが行ってなさそう。
※Lambdaの関数が呼び出されてなさそうな感じがする。

で、Open/SendはできているということはSORACOMまでは行っているような気がする。
※でもコンソール画面のエラーログには出ていないので行っていない可能性もある?

わからん。
ハマりかけている気がするのでいったんここで終了。

追記

Lambdaでの処理が間違っていた

Harvestからぶっこ抜いてきたエンコードされているデータをこれを使って検証にしてみることにする。
[データ]
{"payload":"eyJsYXQiOjAuMDAwMDAwLCJsb24iOjAuMDAwMDAwLCJ0ZW1wIjoyNS43LCJodW1pIjo1OS4wLCJnX2F2ZSI6MC43MTUsIm1vZGUiOjJ9"}

[一次処理済みデータ]
{"value":"{\"lat\":0.000000,\"lon\":0.000000,\"temp\":25.7,\"humi\":59.0,\"g_ave\":0.715,\"mode\":2}"}

※payloadを変換したあとにvalueが入ってる?

結論
base64.b64decodeで変換した結果の使い方が間違っていたっぽい。
辞書で渡してたから変換後もそのままで扱ってたけど、よく考えたらこれの結果は文字列だった。
なのでjson.loadsしてあげればOK

encoded = event["item"]["payload"]
receive_data = base64.b64decode(encoded).decode()
receive_json_data = json.loads(receive_data)

こんな感じ。
payloadの状態でLambdaでテストしてなかったのが間違いだった。テスト大事。
※payloadを変換した結果にvalueというKeyも入っているのかと思ったらそんなことはなかった。

ダメだった。

が、これでOKかと思ったらそんなことはなかった。
デバイスからだとやっぱり上手くいかず。
やはりAWSまで飛んでいないのか?

一回Harvestへの送信に戻してみる。
特に問題なくできてそう。

で、もう一回Beam。
飛んでこない。
CloudWatch でも確認してみるがやっぱり飛んできてなさそう。

確認のため、BeamからSlackへ飛ばしてみることにする

飛ばせてる。
当たり前だけど、SORACOMまでは来ていることが分かる。
※この辺を簡単にグループ設定/変更できるのはSORACOMのいいところだと思う。

突然の解決

ふと思い立って「事前共有鍵」の設定を追加。
※これがなんだかよくわからなかったので未設定だった。

これでAWSへも送信されるようになった。
AWSへ送信したい場合はこれの設定が不可欠だったのか?

ということでやりたかったことは完成。
ついでに「揺れ」を検知した場合はSlackへの送信するようにしてみた。
※GPSから位置情報が取れなかったとき、適当な位置にポイント作成するようにしてます。

Lambda

import json
import base64

AGOL_applyEdits = r"https://[xxxxxxxx].arcgis.com/[XXXXXXXXXXXX]/arcgis/rest/services/fromsoracom_01/FeatureServer/0/applyEdits" 
Slack_Token = "xoxp-xxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxxxxxxxx"

def lambda_handler(event, context):

    # Base64で変換された結果がpayloadに格納されているのでデコードする
    encoded = event["item"]["payload"]
    receive_data = base64.b64decode(encoded).decode()
    receive_json_data = json.loads(receive_data)

    send_data = CreateAddData(receive_json_data)
    AddFeature(AGOL_applyEdits, send_data)

    if(receive_json_data["mode"] == 2):
        to_slack(receive_json_data)

def CreateAddData(attributes_dic):

    # 測地系設定:固定(WGS1984で設定)
    geometry_dic = {}
    geometry_dic["x"] = attributes_dic["lon"]
    if(0==geometry_dic["x"]):
        geometry_dic["x"] = 140.1
    geometry_dic["y"] = attributes_dic["lat"]
    if(0==geometry_dic["y"]):
        geometry_dic["y"] = 35.1
    spatialReference_dic = {}
    spatialReference_dic["latestWkid"] = 4326
    spatialReference_dic["wkid"] = 4326
    geometry_dic["spatialReference"] = spatialReference_dic

    # パラメータ作成
    devData_dic = {}
    devData_dic["geometry"] = geometry_dic

    # 時間:SORACOMから渡されるUNIX TIMEを使用してもいい。
    from datetime import datetime
    attributes_dic["time"] = datetime.utcnow().strftime('%Y/%m/%d %H:%M:%S')

    devData_dic["attributes"] = attributes_dic

    return devData_dic

def AddFeature(url, devData_dic):
    # 送信する
    # 属性データの辞書をJSONに変換
    json_str = json.dumps(devData_dic)

    # 属性JSONをURLエンコード
    import urllib.parse
    json_q = urllib.parse.quote(json_str)

    # 送信パラメータを構築
    sendData = "adds=" + json_q + "&f=pjson"

    # 送信(POST)
    headers = {"Content-Type" : "application/x-www-form-urlencoded"}
    req = urllib.request.Request(url, headers=headers, data=sendData.encode('utf-8'))
    with urllib.request.urlopen(req) as response:
        response_body = response.read().decode("utf-8")
        json_dict = json.loads(response_body)
        return json_dict

def to_slack(receive_json_data):
    import urllib.request, urllib.parse
    params = {
        "token": Slack_Token,
        "channel": "random",
        "text": "ゆれた気がする\n緯度:{}\n経度:{}".format(receive_json_data["lat"],receive_json_data["lon"])
    }
    p = urllib.parse.urlencode(params)
    url = "https://slack.com/api/chat.postMessage?" + p
    with urllib.request.urlopen(url) as res:
    res.read().decode("utf-8")

ArcGISでの表示

以下で参照することもできます。
https://rio-naka.maps.arcgis.com/apps/webappviewer/index.html?id=3466ae5585364e98ae7e7abdfa4bfb89

Slackでの通知結果

まとめ

とりあえずこれでひとしきりで遊んでみたい連携機能の確認はできたような気がする。
セキュリティ的にはザルなので気を付ける必要はありますが。。

SORACOMのBeam上で複数の送信先に転送ができるといいなぁとも思う。
あと、エラーログ?しかコンソール上では確認できないみたいなので、もうちょっといろいろと参照できると嬉しいかも。Harvestとかだとあまり気にならないが、BeamだとIN/OUTが見たかった。
逆にやりたいことに合わせてグループを作成しておけば、簡単に変更できるというのはGood。