InCaaaan:印鑑マシーンの作り方 上司ツール編

#InCaaaanとは？
[InCaaaan:印鑑マシーンの作り方 超概要編](https://qiita.com/oic0310/items/fb0aca7d355df9ddafb3)に、全体的な構成と動画を置いてますので、ご確認ください。
では、早速作り方を！！！！

#InCaaaanの中核は、IBM CloudのNode-RED
主に以下の制御を行っている。

- 部下用の申請Webコンテンツの表示(LINE ID、LINE名取得もする)
- Kintoneに申請データを保存する
- AWSからKintoneのレコードが無事に追加されたことを受ける
- レコードが追加されたことをトリガーにTwillioに自動音声の電話を要求(今回は説明対象外)
- 電子型押印にKintoneのレコード IDとLINE名と金額をMQTTで送る
- 電子型押印からの承認要求を受けて、承認データを生成してKintoneに送信
- Obnizのサーボを動作

<img width="266" alt="スクリーンショット 2019-12-14 20.14.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/49de8307-d546-404e-53ce-7847fa9ee01e.png">


#Kintoneの環境を準備する
##帳票を作成する
Kintoneは非常に使い勝手の良いサービスだけではなく、ユーザガイドも充実してますので、ここであえて解説は必要ないと思います。
デベロッパーに登録すると、無料でかなり楽しめる・・・間違えたかなり試せるので、デベロッパー登録は忘れずに！
登録は、[kintone 開発者ライセンス](https://developer.cybozu.io/hc/ja/articles/200720464)こちらからできます。
###InCaaaanの申請・承認を保管する帳票の作成
帳票には以下の項目を配置した。括弧の中は変数名です。

- レコード番号(recordId)
- 承認キー(Approval)
- LINE ID(LINE_ID)
- 氏名(LINE_NAME)
- LINE プロファイル(LINE_PROFILE)
- 書類名(FileName)
- 金額(currency)
- 申請理由(Reason)

<img width="1278" alt="Kintone" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/bb677881-d062-afcf-9d20-7c475fd377a5.png">

###APIトークンを設定する
設定にある「APIトークン」をクリックします。
<img width="980" alt="スクリーンショット 2019-12-14 19.58.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/b904f5ed-336a-bb38-b49c-0f8bd1db4751.png">
APIは、レコード閲覧、レコード追加、レコード編集にチェックをしておく。なんと！これだけで終わり！
<img width="1167" alt="スクリーンショット 2019-12-14 20.06.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/1414155c-939e-8304-f33f-02e485f91285.png">

なぜレコードの追加を受けるか、というと、部下用のUIとしてLINE Front-end Frameworkを使って表示しているWebサイトを入力元にしたいと考えたからである。
当初、ブラウザからKintoneにログインして・・・という考えもあったが、若い人がいつも使い慣れているインターフェースを入り口にして、いつか認証の連携もLINEと行えば、LINE IDで様々なことができるのではないか？と考えたからです（ただし、ヒーローズリーグでは実装できてなかった）

![PNGイメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/ae3a7a63-a34e-e04c-db00-b510de177d8d.png)

###部下からの申請をNode-REDで受信する

部下がLINE上で入力したデータを受信する仕組みです。
構造は至ってシンプルで、POSTされたデータをKintone APIを使ってKintoneにレコード追加するのみとなります。

<img width="730" alt="スクリーンショット 2019-12-14 23.00.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/39d41ffb-9714-def0-6be8-07bd58d4c9e7.png">

「Headerを追加」というファンクションでKintone APIを呼ぶ前の準備をしています。
構造は至ってシンプルで、APIキーをセットして、部下からPOSTされてきたデータをKintoneのお作法にならってJSON形式に整形してあげるだけです。

```javascript:KintoneAPIを呼ぶ前の準備

var api_key = 'KintoneのAPIキー';
msg.headers = {
    'X-Cybozu-API-Token': api_key,
    'Content-Type': 'application/json',
    'Authorization': 'Basic ' + api_key   
    //"X-HTTP-Method-Override": "GET"
};
msg.payload = {
			app: 1,
			record: {
				'LINE_ID': {
					value: msg.payload.LINE_ID
				},
				'LINE_NAME': {
					value: msg.payload.LINE_NAME
				},
				'LINE_PROFILE': {
					value: msg.payload.LINE_PROFILE
				},
				'FileName': {
					value: msg.payload.FileName
				},
				'currency': {
					value: msg.payload.currency
				},
				'Reason': {
					value: msg.payload.Reason
				}
			}
}
return msg;
```

###レコードが追加された場合に動作するWebhookを設定する
別のインターフェースを用いたことにより、KintoneにAPIの追加が完了して、次の承認要求のプロセスを動作させる為に、Webhookを利用した。
アプリの設定のWebhookを選択する。
<img width="841" alt="スクリーンショット 2019-12-14 20.29.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/14a940ac-f742-7b1a-422d-9c2dd3b2bea9.png">
Webhookの画面の＋ボタンを選択します。
<img width="1267" alt="スクリーンショット 2019-12-14 20.30.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/700bc713-1e2e-9a8f-656d-896cf49cc2f3.png">

ここでは、URLと通知を送信する条件として「レコードの追加」を選択して保存するだけとなる。
<img width="1271" alt="スクリーンショット 2019-12-14 20.31.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/25cde607-b809-a49a-dd9a-eca43793b038.png">

追加が完了すると、以下のように追加されている。緑のチェックアイコンは正常に接続された場合なので、ここが赤いXマークだとWebhookが動作してないのでご注意を。
<img width="1270" alt="スクリーンショット 2019-12-14 20.33.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/5602e711-399a-4cde-9481-355b1fcfa230.png">

#AWSの環境(KintoneからWebhookを受ける)を整える
KintoneからWebhookの通知を送る方法を確認したところ、以下公式ページから拝借した図では、Zapier、Microsoft Flow、IFTTTなどの紹介があった。しかし、これらは何か追加された、削除された、というような簡単なリクエストを受ける場合には有益なサービスであるが、**部下からの入力データを使い回したい**のニーズに適用できなかった。
<img width="750" alt="スクリーンショット 2019-12-14 20.38.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/90800074-23db-0ae2-1b0f-f02565fc2f74.png">

その為、AWSで自作することにした。というか、AWSで作る方が早いと思った。

##Lambdaファンクションを作る
AWSと言っても沢山のサービスがある。AWSマネジメントコンソールの「サービスを検索する」の部分に「Lambda」と入力する

<img width="1139" alt="スクリーンショット 2019-12-14 20.37.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/b7b0dcbd-6bdd-d840-3caf-9c2c3a681220.png">

AWS Lambdaの画面に遷移するので、早速「関数の作成」を選択する

<img width="1280" alt="スクリーンショット 2019-12-14 21.39.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/0a82d606-e8b6-0fb1-d039-e68d8415af75.png">

「一から作成」を選択し、ランタイムは「Python 3.8」を選択して作成する

<img width="1244" alt="スクリーンショット 2019-12-14 21.42.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/c0341e43-7797-24cc-a806-7e3c260d7b0b.png">

「関数コード」の部分に以下のコードを入力する

<img width="1279" alt="スクリーンショット 2019-12-14 21.45.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/07e02d3c-c39d-4b81-ac2f-a2ac769a2224.png">

プログラムはものすごく単純で、KintoneのWebhookで通知されてきたリクエストに格納されているデータを抽出し、IBM CloudにPOSTしている

```python:lambda_function.py
import json
import logging
 
logger = logging.getLogger()
logger.setLevel(logging.INFO)
 
def lambda_handler(event, context):
    logger.info(event['record'])
    logger.info(u'レコード番号 : ' + event['record']['recordId']['value'])
    logger.info(u'金額 : ' + event['record']['currency']['value'])
    logger.info(u'作成日時 : ' + event['record'][u'作成日時']['value'])
    
    import urllib.request
    url = 'IBM CloudのNode-REDのポスト先：電子型押印に通知'
    params = {
        'recordId': event['record']['recordId']['value'],
        'LINE_ID': event['record']['LINE_ID']['value'],
        'LINE_NAME': event['record']['LINE_NAME']['value'],
        'LINE_PROFILE': event['record']['LINE_PROFILE']['value'],
        'FileName': event['record']['FileName']['value'],
        'currency': event['record']['currency']['value'],
        'Reason': event['record']['Reason']['value'],
        'date':event['record'][u'作成日時']['value']
    }
    
    req = urllib.request.Request('{}?{}'.format(url, urllib.parse.urlencode(params)))
    with urllib.request.urlopen(req) as res:
        body = res.read()

```

##API Gatewayを設定する

AWSマネジメントコンソールの「サービスを検索する」の部分に「API Gateway」と入力する

<img width="1139" alt="スクリーンショット 2019-12-14 20.37.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/b7b0dcbd-6bdd-d840-3caf-9c2c3a681220.png">

Amazon API Gatewayの画面が表示されたら、さっそく「APIを作成」をクリックする

<img width="1279" alt="スクリーンショット 2019-12-14 21.57.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/7985f0bf-28b4-f2b1-65c1-f0cf95d16568.png">

REST APIの「構築」を選択します。
<img width="1275" alt="スクリーンショット 2019-12-14 21.58.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/b04c7878-82cf-0525-b326-1c4812353d09.png">

API名を入力して、「APIの作成」を選択しましょう

<img width="1280" alt="スクリーンショット 2019-12-14 22.00.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/e68b7a9b-dc39-f6b7-7937-fb4831649054.png">

「アクション」というタブの中に、「メソッドの作成」があるので選択してください。

<img width="1277" alt="スクリーンショット 2019-12-14 22.01.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/46963b56-4312-b2af-10e5-66b4d07cb6ab.png">

HTTPのコマンドが表示されるので「POST」を選択します。

<img width="678" alt="スクリーンショット 2019-12-14 22.02.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/4d21643d-b069-9660-615e-e2c0d39ad64f.png">

POSTのセットアップ画面が表示されるので、作成したLambda関数を入力しましょう

<img width="1018" alt="スクリーンショット 2019-12-14 22.03.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/f1861e29-2aff-fe28-f3ab-27d7f149494f.png">

といっても、なんのこっちゃ、という話になるので、Lambda関数の部分には、Lambda関数を作成していた画面の右上に表示されている「ARN」の部分(以下の図)の文字の羅列をコピーして貼り付けると良いです。そして保存しておしまいです。

<img width="1276" alt="スクリーンショット 2019-12-14 22.05.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/f95f1f95-975d-eb4c-4785-5c870550a9c9.png">

次に、「ステージ」を作成します。と、いいつつ、「APIのデプロイ」を行います。

<img width="573" alt="スクリーンショット 2019-12-14 22.16.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/9983dfeb-217e-d5b5-8402-fbdbc458da32.png">

APIのデプロイを選択すると、新しいステージが作れるので、新しいステージを選択して、ステージ名を入力後、「デプロイ」を選択する。

<img width="602" alt="スクリーンショット 2019-12-14 22.17.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/86a2cb50-333e-696a-5933-e30c9d5e3354.png">

なんと、これで呼び出し可能なURLが発行されました。
本来は、APIキーの発行だ、とか、VPCリンクだ、とかセキュリティを担保する設定も必要だが、今回はプロトタイプということで設定を省略しています。

<img width="1277" alt="スクリーンショット 2019-12-14 22.19.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/db99bad5-4fad-7a29-80cc-000578f484cc.png">

このURLをKintoneのWebhookの追加に出てきたURLに設定するだけで、Kintoneにレコードが追加されるたびに、AWSに送信される仕組みとなる

<img width="1271" alt="スクリーンショット 2019-12-14 20.31.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/25cde607-b809-a49a-dd9a-eca43793b038.png">

#AWSからPOSTを受けるIBM Cloud Node-REDで受信後、MQTTのメッセージをパブリッシュする

ここは、HTTPのPOSTを受信し、MQTTでメッセージをパブリッシュするというシンプルな説明となる。

<img width="601" alt="スクリーンショット 2019-12-14 22.26.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/6d16f5b1-20f1-504a-db3a-b1c15e963878.png">

ここでは、「MQTTで送信」というファンクションが肝になる。
記載しているプログラムはシンプルで、AWSからPOSTされたデータ(KintoneレコードID、LINE名、金額)を、アンダーバーで連結して、新しいmsg2.payloadという変数に格納しなおしているだけとなる。

```javascript:MQTTで送信
var msg2 = {};
msg2.topic='TEST'
msg2.payload=msg.payload.recordId + "_" +msg.payload.LINE_NAME + "_" + msg.payload.currency;
return msg2;
```

以下にNode-REDでインポート可能なものを置いておきます。
MQTTはCloudMQTTを使用しています（フリーの範囲で十分使える）

```javascript:関連するNode-RED
[{"id":"f50b99c1.829b18","type":"http in","z":"38dd45d0.ae9242","name":"申請レコードが追加された","url":"/kintoneData","method":"get","upload":false,"swaggerDoc":"","x":130,"y":800,"wires":[["a7540857.d977d","4c688ee7.9e7a08","11a5ece2.3aa2fb"]]},{"id":"a7540857.d977d","type":"debug","z":"38dd45d0.ae9242","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":470,"y":760,"wires":[]},{"id":"15081bde.4256d4","type":"mqtt out","z":"38dd45d0.ae9242","name":"","topic":"TEST","qos":"2","retain":"false","broker":"33535a31.0e74ee","x":710,"y":800,"wires":[]},{"id":"4c688ee7.9e7a08","type":"function","z":"38dd45d0.ae9242","name":"MQTTで送信","func":"var msg2 = {};\nmsg2.topic='TEST'\nmsg2.payload=msg.payload.recordId + \"_\" +msg.payload.LINE_NAME + \"_\" + msg.payload.currency;\nreturn msg2;","outputs":1,"noerr":0,"x":410,"y":820,"wires":[["15081bde.4256d4"]]},{"id":"33535a31.0e74ee","type":"mqtt-broker","z":"","name":"","broker":"xxx.cloudmqtt.com","port":"14263","clientid":"","usetls":false,"compatmode":true,"keepalive":"15000","cleansession":true,"birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":"","closeTopic":"","closeQos":"0","closeRetain":"false","closePayload":"","willTopic":"","willQos":"0","willRetain":"false","willPayload":""}]
```

#電子型押印(M5StickC)の準備

電子型押印は、MQTTでメッセージを受ける仕組みしています。
受けたメッセージを、LCDに表示し、LCDにメッセージが表示された状態で、Y軸方向に1.5Gの重力を加えれば承認された、解釈してMQTTメッセージをパブリッシュします。

ほとんど、ヒーローズリーグ直前で作ったので、スピード重視にUI Flowを使いました。

<img width="701" alt="スクリーンショット 2019-12-14 22.40.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/60276d61-d1a2-8ca9-6155-40250d54d1dd.png">

すごくシンプルで、MQTTブローカーと接続した後、TESTというトピックをサブスクライブした時に、

- 受信データをLCDに表示する
- Y軸の加速度1.5Gが来るまでループする
- Y軸の加速度1.5Gがきたら、トピック"InCan"にメッセージにTESTトピックで受信したデータをパブリッシュ
- Y軸の加速度1.5Gがきたら、LCDに_表示を設定
- ループを抜け出す(また、TESTトピックが来るまで待機）

という流れである。めっちゃシンプル！

#電子型押印の承認モーションをNode-REDで受信する

M5StickCからパブリッシュされたInCanというメッセージを受信して処理を開始します。

<img width="468" alt="スクリーンショット 2019-12-14 22.47.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/7c17eb7a-c952-a240-6d74-b384c73846a1.png">

やることは、２つで

- 承認した対象のレコードを更新する為、Kintone APIを起動する
- ObnizのWebhookを呼び出す

のみです。

```html:サーボを動かすだけ
<!-- HTML Example -->
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <script src="https://obniz.io/js/jquery-3.2.1.min.js"></script>
  <script src="https://unpkg.com/obniz@2.5.0/obniz.js"></script>
</head>
<body>

<script>
var obniz = new Obniz("40997538");
obniz.onconnect = async function () {
  var servo = obniz.wired("ServoMotor", {gnd:0 , vcc:1 , signal:2 });
  servo.angle(0);
  await obniz.wait(2000);
  servo.angle(55);
  await obniz.wait(2000);
   servo.angle(0);
  await obniz.wait(2000);
}
</script>
</body>
</html>

```

MQTTで制御しようかと思いましたが、

```html:【これはボツなので参考にしないで】MQTTのメッセージで制御

<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
    <link rel="stylesheet" href="/css/starter-sample.css">
    <script src="https://obniz.io/js/jquery-3.2.1.min.js"></script>
    <script src="https://unpkg.com/obniz@2.5.0/obniz.js" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script>
  </head>

  <body>

    <div id="obniz-debug"></div>
    <div id="print">----</div>

    <script>

      var obniz = new Obniz("Obniz");

      // MQTTクライアント生成
      var client = new Paho.MQTT.Client("MQTTブローカ接続先", ポート,"aaa");

      // コールバックの定義
      client.onConnectionLost = onConnectionLost; // 接続が切れたとき
      client.onMessageArrived = onMessageArrived; // メッセージ受信したとき

      // 接続
      var options = {
        useSSL: true,
        userName: "ユーザID",
        password: "パスワード",
        onSuccess:onConnect, // 接続したときのコールバック
        onFailure:doFail // 失敗したときのコールバック
      }
      client.connect(options);

      // 接続したとき
      function onConnect() {
        console.log("onConnect");

        // トピックを購読する
        client.subscribe("メッセージ");

        // トピックにメッセージを発行してみる
        //message = new Paho.MQTT.Message("");
        //message.destinationName = "メッセージ";
        //client.send(message);
      }

      // 失敗したとき
      function doFail(e){
        console.log(e);
      }

      // 接続が切れたとき
      function onConnectionLost(responseObject) {
        if (responseObject.errorCode !== 0) {
          console.log("onConnectionLost:"+responseObject.errorMessage);
        }
        client.connect(options);
      }

      // メッセージを受信したとき
      function onMessageArrived(message) {
        console.log("onMessageArrived:"+message.payloadString);
        // テキスト表示
        $('#print').text(message.payloadString);
      }
    </script>
  </body>
</html>

```

サーバレスイベントに、サーボを動かすプログラムを設定するのみとなります。
サーボでキレイに押印するのは、割と苦労したので、ここでは伏せておきます。

<img width="1274" alt="スクリーンショット 2019-12-14 22.56.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/17e1a2eb-a35e-cdc6-4dd1-0d0c118cd57f.png">

今回、ヒーローズリーグに向かう道中に輸送している最中にサーボが死ぬ(電源入れてもトルクが全くない状態）、会場で大手術をするというハプニングがありました。

<img width="484" alt="スクリーンショット 2019-12-14 23.08.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/24e57f97-b098-c8e6-f3dc-0887e759797e.png">

まあ、本番前には無事に治ったんですが、
正直なところ今回は物理押印をキレイに押す、という難しさを克服する為、

- 木を加工してサーボとObnizを取り付ける
- 取り付けた場所の距離を考えて、スポンジ、印鑑台を設定
- スポンジとサーボを動かす角度を調整する

というのが、一番作るのが苦労した部分だった・・・
手が痛くなるくらい、木を削り、角度を測り、何度も何度もテストしてレッドハッカソンよりも進化させたという・・・・

<img width="483" alt="スクリーンショット 2019-12-14 23.08.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123800/a881c77c-79f8-300c-9dc3-ed5745b9dd0b.png">