お試しで激ゆるデバッグボタンを作ってみた(追記版)


本記事は「SORACOM LTE-M Button powered by AWS Advent Calendar 2018」12月25日(火) に投稿するはずだったものです。
が、すでに翌日になってしまいました。ごめんなさい。皆さん、手洗いうがい励行です。
https://qiita.com/advent-calendar/2018/soracom

SORACOM LTE-M Button powered by AWS (以降、ボタンと表記) についてはこちらになります。
https://soracom.jp/products/gadgets/aws_button/

※前半がグダグダするのでボタンマイスターな方はサクッと飛ばしてください。

兎にも角にもまずはボタンをなんとなく使ってみる

様々なところでボタンが紹介されているところを見てきましたが、ざっとまとめると以下の流れになるはず。

  1. SORACOMのコンソールから何か登録
  2. AWSのコンソールから何か登録
  3. あとはAWS Lambdaでヒャッハー

とはいえ、具体的な使い方なんてこれっぽっちも記憶に無いので先生のお世話になります。
(つまりググる)

ひとまずググってみる

『SORACOM Button 使い方』で、ググり上から1つずつつまみ食い。
マニュアル嫌いな自分にとっては以下がいい感じでした。
https://blog.soracom.jp/blog/2018/11/07/tips-of-soracom-lte-m-button/
→ 『登録方法』のところに書いてあるように、SORACOMのUser ConsoleからMyボタンを登録。

そして無駄にボダンをポチッと一発(clickType=SINGLE)。。。『残りクリック回数』が1500から見事に1499に!!!
(『こんなに簡単につながるなんて!』って軽く感動しましょう)
これでUser Consoleとはひとまずお別れ。

AWS IoT 1-Clickデビュー

次にAWS IoT 1-Clickにて適当にデバイス登録します。
そして今回も登録のために無駄にボタンをポチっと一発(clickType=SINGLE)。。。
(SORACOMのUser Consoleより残りクリック回数が1つ減っているのがわかると思います)

いよいよプロジェクトを作成

ひとまずボタンからLambdaまで開通確認を行いたいので、サンプル通りにやっちゃいます。
SORACOMのブログにいいことがたくさん書いてあるのでデベロッパー魂なんて一旦捨てて依存しまくります。

https://dev.soracom.io/jp/start/aws_button_slack/
→ SMSでボタンを契機としたメッセージが受信できたところで満足します。

開通したのでシンプルにボタンを堪能

SORACOM LTM-M Button ⇒ AWS IoT 1-Click ⇒ AWS Lambda ⇒ Amazon SNS ⇒ スマホでSMS受信

  • ポチっとする(clickType=SINGLE)
  • ポチポチっとする(clickType=DOUBLE)
  • ポチーっとする(clickType=LONG)

気になったのでトピック的なメモ

  • 深爪した直後にボタンのリアカバーを開けるのはなかなか苦戦する(電池を入れたり、製造番号を確認したり、最初に襲い掛かる試練)
  • SORACOMのUser ConsoleのUIにて、[ボタン登録]ダイアログに『製造番号』って表記があるけど、ボタン側に表記がDSNだから『DSN(製造番号)』って表記のほうが一貫性があっていいなぁ(ボソッ)

以下、追記

いよいよ激ゆるデバッグボタンを作成

プログラムに専用のデバッグ文を追加すると『ブレークポイント』としてそこで処理が一時的に停止する。で、ボタンを押すと処理を再開する。そんな感じのやつに仕上げたい。

『ひとまずこんな感じ』イメージ

図にすると以下のような感じ。

パッと思い浮かぶAWSのアーキテクチャでいろいろ悶々となる

ボタンを契機としたLambda Functionの実行やデバッグ文からのAPI実行など、『よくある構成』だとこんな感じかと思います。

ただこの構成だとLambda Function内でボタンが押されるまでグルグルするのでコスパ的にあんまりよろしくないなぁと思ったので、グルグルするのはプログラムのデバッグ文内に変更。で、今回はシンプル構成がモットーなので、ボタンが押されたことやデバッグ文に突入したことを記録する程度の記録はDynamoDBではなくS3に変更。

で、悶々とした結果のアーキテクチャ

以下が新しいアーキテクチャです。

ポイントは2つ。

  • コスパを考慮して、ボタンが押されれまでプログラムのデバッグ文内でグルグル
  • コスパを考慮して、記録を残すDB的なものもあえてS3(そもそもDB的な操作もしないのでS3で十分)

ざっくりと動作の解説

以下のような感じ。

  1. プログラム開始(hello.sh)
  2. デバッグ文の処理(前編)
    1. ブレークポイントに突入したことをS3に記録(lambda_fuction_1)
    2. ボタンが押されたことの記録が追加されるまでグルグル
  3. ボタンの処理
    1. ボタンが押されてからLambda Functionがなんやかんや待つ
    2. S3にボタンが押されたことを記録(lambda_function_2)
  4. デバッグ文の処理(後編)
    1. グルグルしていた処理から抜ける(lambda_function_3)

モロモロの処理はこんな感じ

では、具体的にどんな処理なのか解説していきます。

lambda_fuction_1

ブレークポイントに突入したことをS3に記録します。空のオブジェクトを置くだけの簡単な処理です。

lambda_function_1.py
import json
import boto3
from datetime import datetime

print('Loading function')

s3 = boto3.resource('s3')

def lambda_handler(event, context):
    bucket = 'buttondebug'
    key = datetime.now().strftime('%Y-%m-%d-%H-%M-%S') + '_BREAK'
    file_contents = ''
    obj = s3.Object(bucket,key)
    obj.put( Body=file_contents )

    return 'BREAK'

lambda_function_2

S3にボタンが押されたことを記録しますが、せっかくなのでボタンのクリックタイプも記録します。

lambda_function.py
import json
import boto3
from datetime import datetime

print('Loading function')

s3 = boto3.resource('s3')

def lambda_handler(event, context):
    bucket = 'buttondebug'
    clickType = event["deviceEvent"]["buttonClicked"]["clickType"]
    key = datetime.now().strftime('%Y-%m-%d-%H-%M-%S') + '_' + clickType
    file_contents = ''
    obj = s3.Object(bucket,key)
    obj.put( Body=file_contents )

    return {
        'statusCode': 200,
        'body': json.dumps(clickType)
    }

lambda_function_3

APIの仕様として、ブレークポイントに突入した直後は『WAIT』、ボタンが押されたら『RUN』を返します。やっていることは単純でS3のバケット内の最新オブジェクトをチェックしているだけです。

オブジェクト名は『<日時>_サフィックス』となっています。サフィックスとして、ブレークポイントはBREAK、ボタンタイプとしてシングルクリックはSINGLE、ダブルクリックはDOUBLE、長押しはLONGなのでそのまま採用します。今回はボタンをどう押してもブレークポイントから抜け出すようにしているので、BREAK以外のオブジェクトが現れたら『RUN』を返しています。

lambda_function_3.py
import json
import boto3

print('Loading function')

client = boto3.client('s3')

def lambda_handler(event, context):
    bucket = 'buttondebug'
    response = client.list_objects_v2(Bucket=bucket)

    if 'Contents' in response:
        keys = [content['Key'] for content in response['Contents']]

    keys.reverse()
    currentStatus = keys[0]

    if currentStatus.endswith('BREAK'):
        return 'WAIT'

    return 'RUN'

hello.sh

メインのプログラムです。というか、シェルです。

hello.sh
#! /bin/bash

# シェル関数
function button_debug(){
        # ブレークポイント突入
        curl https://abcdefg.execute-api.ap-northeast-1.amazonaws.com/production/breakpoint >/dev/null 2>&1
        sleep 2

        # ボタンが押されるまでグルグル
        while true;do
                STATUS=`curl https://abcdefg.execute-api.ap-northeast-1.amazonaws.com/production/status 2>/dev/null`
                [ $STATUS = '"RUN"' ] && break
                sleep 2
        done
}

# メイン処理
echo '<<<< Start >>>>'

# デバッグ文(button_debug)でメッセージ表示を挟み込んでみた
button_debug
echo 'Hello LTE-M Button !!!'
button_debug

echo '<<<<  End  >>>>'

実行結果は以下のようになります。
シェルを実行すると途中で処理が止まります。そしてボタンを押して数秒待つと『Hello LTE-M Button !!!』が表示されてシェルは終了します。

$ ./hello.sh
<<<< Start >>>>
Hello LTE-M Button !!! ← ボタンを押すと表示
<<<<  End  >>>>

注意

今回は『お試し』なので処理はあれこれ手を抜きまくりです。
エラー処理やらセキュリティ設定をあえて外していますが、あまりオススメしませんのでアシカラズ。