mockmockで簡単なIoTシステムを開発してみよう

この記事は[mockmockアドベントカレンダー](https://qiita.com/advent-calendar/2019/mockmock)24日目の記事です。
クリスマスイブの今日はmockmockを使って簡単なIoTシステムを開発します。

ただし、mockmockのアドバンスドオプションの一つである「DataRecorder」を使って、
**デバイスから送信されたデータの確認・クラウドへの再送を行い、スマートに開発していく点**に注目です。

## 開発するシステム
### システム名
システム名は**「Suwarin」**とします。
某子供向け番組のキャラクターをリスペクトして名付けました。

### 仕様
自分自身の着席状況をSlackへ通知するIoTシステムです。
自分が着席したら「着席しました」、離席したら「離席しました」とメッセージを送信します。

## システム構成

今回、デバイスにはM5StickCを使用します。クラウドはAWSでIoT Core, DynamoDB(Stream), Lambdaを使用します。 

![Untitled_mockmock_-_Cacoo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/b12cdfff-1bf8-9afd-9ff5-a225eb8d4649.png)

肝心のセンサーですがたまたま手元にあった「[Seed社 Grove-Ultrasonic Ranger](http://wiki.seeedstudio.com/jp/Grove-Ultrasonic-Ranger/)」を使用します。

![Grove_-_超音波レンジャー_-_Seeedウィキ（日本語版）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/4bb9993c-9f4e-be5a-5980-d4b4cb17beed.png)

検出した距離が近いときは「着席中」、遠いとき(未検出時の値)のときは「離席中」と判定します。

## [デバイスの開発・テスト]M5StickC→mockmockにデータを送る

まずはM5StickCでプログラムを起動して、mockmockにデータを送ります。

### mockmock DataRecorderの設定

#### エンドポイントの作成
mockmockの[ログインページ](https://console.mock-mock.com/users/sign_in)からログイン後、「データレコーダー」→「データセット作成」の順でクリックしてください。

![IoT開発を加速する仮想デバイス作成サービス___mockmock.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/82f45175-3e47-7ba3-0faa-db0ce80a146c.png)

「データセット新規作成」フォームが表示されるので、任意の名称と説明を入力後、「登録」をクリックしてください。

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f36303939362f31343437653835642d323661372d343030342d376566622d3439346236613464373630392e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/fa868a57-2a70-7f9f-9e67-79b3c9d92fc0.png)

「データセット一覧」に作成したデータセットが表示されたら「詳細」をクリックしてください。
![IoT開発を加速する仮想デバイス作成サービス___mockmock.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/955ce0d2-a187-9d31-6687-27b5053f080e.png)

「エンドポイント」の「MQTTS」の内容をメモしておいてください。後ほど使用します。

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f36303939362f38383863316432312d353135662d343233362d323434642d6563306338373630636263352e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/77a9e156-d4f6-aa63-a965-c299b3bd23c5.png)


#### 証明書の作成
「データレコーダー」→「証明書：新規作成」の順でクリックしてください。
![IoT開発を加速する仮想デバイス作成サービス___mockmock_と_Slack___random___fusic___1_個の新しいアイテム.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/ce0905c0-ce4f-47ba-a53e-bd4ea61502ec.png)

証明書ファイルのダウンロードがはじまりますので任意の場所に保存してください。後ほど使用します。
作成した証明書が表示されたら「有効化」をクリックしてください。

![IoT開発を加速する仮想デバイス作成サービス___mockmock.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/b754e994-798d-27f6-c7ff-b3c8780ab231.png)

### デバイス側の実装
以下ソースコードをビルドして、M5StickCへ書き込んでください。
`{{  }}`で囲まれた文字列はmockmockの設定やご使用のWi-Fiアクセスポイントに合わせて書き換える必要があります。
Root CAは[こちら](https://www.amazontrust.com/repository/AmazonRootCA1.pem)を参照。

```c:suwarin
#include <M5StickC.h>
#include "Ultrasonic.h"
#include "cloud.h"

#define FILTER_RATE 0.95
Ultrasonic ultrasonic(33);
float filtered_value = 0;

void setup_lcd() {
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextSize(1);
  M5.Lcd.setRotation(1);
  M5.Lcd.setCursor(0, 0);  
}

void setup_serial() {
  Serial.begin(9600);  
}

void setup() {
  M5.begin();
  
  setup_lcd();
  setup_serial();
  setup_mqtt();
}

float rc_filter(float raw_value) {
  filtered_value = (1 - FILTER_RATE) * raw_value + FILTER_RATE * filtered_value;
  return filtered_value;
}

void loop() {
  mqtt_connect();
  for(int i = 0 ; i < 10 ; i++) {
    M5.Lcd.setCursor(0, 30);
    M5.Lcd.printf("%d cm     ", ultrasonic.MeasureInCentimeters());
    M5.Lcd.println("");
    delay(100);
  }
  mqtt_publish_measure(rc_filter(ultrasonic.MeasureInCentimeters()));
}
```

```c:cloud.h
#define WIFI_SSID "{{Wi-FiのSSID}}"
#define WIFI_PASSWORD "{{Wi-Fiのパスワード}}"

#define CLOUD_ENDPOINT "<実際のエンドポイントに置き換えてください>.iot.ap-northeast-1.amazonaws.com"
#define CLOUD_PORT 8883
#define CLOUD_TOPIC "{{DataRecorderが表示するtopic名に置き換えてください}}"

// {{実際の証明書に置き換えてください}}
#define ROOT_CA "-----BEGIN CERTIFICATE-----\n"\
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"\
"xxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"\
"-----END CERTIFICATE-----\n"
#define CERTIFICATE "-----BEGIN CERTIFICATE-----\n"\
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"\
"xxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"\
"-----END CERTIFICATE-----\n"
#define PRIVATE_KEY "-----BEGIN RSA PRIVATE KEY-----\n"\
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"\
"xxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"\
"-----END RSA PRIVATE KEY-----\n"\

void setup_wifi();
void setup_mqtt();
void mqtt_connect();
void mqtt_publish_measure(int centimeters);
```

```c:cloud
#include <M5StickC.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include "cloud.h"

WiFiClientSecure https_client;
PubSubClient mqtt_client(https_client);

char pub_message[256];

void mqtt_callback (char* topic, byte* payload, unsigned int length) {
}

void setup_mqtt() {
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    M5.Lcd.printf(".");
  }
  M5.Lcd.println("");
  M5.Lcd.println("WiFi connected");
  M5.Lcd.printf("IP address: ");
  M5.Lcd.println(WiFi.localIP());
  
  https_client.setCACert(ROOT_CA);
  https_client.setCertificate(CERTIFICATE);
  https_client.setPrivateKey(PRIVATE_KEY);
  mqtt_client.setServer(CLOUD_ENDPOINT, CLOUD_PORT);
  mqtt_client.setCallback(mqtt_callback);
}

void mqtt_connect() {
  if (!mqtt_client.connected()) {
    while (!mqtt_client.connected()) {
      String clientId = "1";
      if (!(mqtt_client.connect(clientId.c_str()))) {
        Serial.println(mqtt_client.state());
        delay(5000);
      }
    }
    M5.Lcd.println("MQTT Connected!");
  }
  mqtt_client.loop();
}

void mqtt_publish_measure(int centimeters) {
  memset(pub_message, 0x0, sizeof(pub_message));
  sprintf(pub_message, "{\"centimeters\":%d}", centimeters);
  mqtt_client.publish(CLOUD_TOPIC, pub_message);
  M5.Lcd.println("MQTT Published!");
}
```

### テスト
M5StickCの電源をONしてください。
次に「データレコーダー」→「証明書：新規作成」の順でクリックすると、デバイスから送信されたデータを確認できます。

![IoT開発を加速する仮想デバイス作成サービス___mockmock.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/ea324bd0-c3fc-8dc5-a090-06add8d2a554.png)

## [クラウドの開発・テスト]mockmock→AWS IoTにデータを送る
### AWSの設定
以下の記事の「IoT Coreの証明書を作成する」〜「AWS IoT Coreのエンドポイントの確認」までの手順を実施ください。
[mockmockでAWS IoT Coreにデータを送るプロジェクトを作成する](https://qiita.com/Y_uuu/items/6e0e7e532b3e65672d91#iot-core%E3%81%AE%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B)

### mockmockの設定
「プロジェクト一覧」→「プロジェクト作成」の順にクリックしてください。
![IoT開発を加速する仮想デバイス作成サービス___mockmock.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/af9fcaa3-4489-819a-f88d-b97a69001947.png)

フォームに以下の通り入力して、「登録」をクリックしてください。
「送信先ホスト」や「各種証明書ファイル」は先の手順で確認した内容・ファイルに変更ください。

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f36303939362f65363064386339662d396530362d363864332d376664642d3061356564663435356233322e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/f50f3ba0-ac73-64d5-b0d1-00e27c4892b2.png)

「リプレイヤー」を作成します。
「リプレイヤーの右側の＋ボタン」をクリック後、フォームを以下の通り入力して、「登録」をクリックしてください。
![IoT開発を加速する仮想デバイス作成サービス___mockmock.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/7fcaf1d2-a99a-55c7-6b54-21f7e40b7913.png)

「mockグループ」を作成します。
「mockグループの右側の＋ボタン」をクリック後、フォームを以下の通り入力して、「登録」をクリックしてください。
![IoT開発を加速する仮想デバイス作成サービス___mockmock.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/cf7c2530-4d03-eef2-2f71-6fc9c3bf1ecb.png)

「送信設定」を作成します。
「mock管理」→「送信設定編集」の順にクリックしてください。
![IoT開発を加速する仮想デバイス作成サービス___mockmock.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/2c01fc6a-f0cf-4799-0ba4-104f6cc33fbe.png)

以下の通り入力して「登録」をクリックしてください。
「Topic」はデータレコーダーで使用していたエンドポイントに合わせておくと、後々楽に作業を進められます。
![IoT開発を加速する仮想デバイス作成サービス___mockmock.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/884c4b98-5206-c6ea-191e-eddfe3cf97c7.png)

### テスト
「mockグループ：suwarin」→「mock作成」の順にクリックしてください。
![IoT開発を加速する仮想デバイス作成サービス___mockmock.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/3b60195d-61eb-b317-e583-c9ab7f659a5c.png)

以下の通り入力して「登録」をクリックしてください。
![IoT開発を加速する仮想デバイス作成サービス___mockmock.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/020e3361-e5a1-ccd3-f4c9-ac05b086668c.png)

表示されたmockの「操作」→「起動」をクリックします。
![IoT開発を加速する仮想デバイス作成サービス___mockmock.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/eecf6c7b-7fcb-e0fe-29b7-8439f53bd7ac.png)

AWS IoT Coreの「テスト」をクリックし、以下の通り入力して「トピックへのサブスクライブ」をクリックしてください。
![AWS_IoT.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/09d0c430-a377-f74c-0e7f-4b510ceb96f4.png)

mockが起動すると、データレコーダ内のデータが送信されます。

![AWS_IoT.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/2b692e60-6490-5888-4e42-5720367a6749.png)

## [クラウドの開発・テスト]DynamoDB, Lambdaの動作確認をする
この手順に関しては本記事のメインの部分から外れるので、簡単に紹介します。

まずAWS IoT Coreのルールを以下の通り作成します。
![AWS_IoT.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/18a88aac-5600-b27e-2b7b-095222438c70.png)

アクションは以下の通りDynamoDBへ値を書き込むこととします。
![AWS_IoT.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/cb06870d-dcbc-8c9b-d222-92ef2815d8eb.png)

DynamoDBのコンソールにてストリームを有効にしておきます。
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f36303939362f32643032323139652d636163312d343239652d356139392d6436613563656432363461372e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/21ca17d3-7bcf-ca77-054a-dbb3b19c07e6.png)


DynamoDBのストリームをトリガーとするLambdaを作成します。

```ruby:lambda_function.rb
require 'json'
require 'slack-notifier'

def lambda_handler(event:, context:)
  event['Records'].each do |record|
    next unless record['eventName'] == 'MODIFY'
    
    old = record['dynamodb']['OldImage']['payload']['M']['centimeters']['N'].to_f
    new = record['dynamodb']['NewImage']['payload']['M']['centimeters']['N'].to_f
    next if sit_down?(old) == sit_down?(new)
    
    notifier = Slack::Notifier.new('<WEBHOOK_URLに置き換えてください>')
    if sit_down?(new)
      notifier.ping('着席しました')
    else
      notifier.ping('離席しました')
    end
  end
end

def sit_down?(centimeters)
  centimeters.to_f < 100.0
end
```

以上の設定・実装を終えた後、mockmockデータを送信してみます。
数値が100を超えたタイミング、100を下回ったタイミングでSlackに通知が飛ぶことが確認できます。

![Slack___nicole-dev___fusic.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60996/52fb4765-3dad-654a-1f2b-93dda08f920c.png)

## [システム統合テスト]M5StickC→AWS IoTにデータを送る

M5StickCのソースコードを変更し、証明書およびエンドポイントをAWS IoT Coreのものに変更すれば
無事IoTシステムが完成です。

## まとめ

mockmockのDataRecorderを使うことで、クラウドなしでデバイスの動作確認、デバイスなしでクラウドの動作確認を行った上で、システム全体をテストすることができました。

IoT開発においてデバイス・クラウドのテストが不十分な状態で、システム統合テストに突入すると不具合の原因調査に膨大な工数がかかるケースが多いです。

mockmockを活用し段階を踏んでテストすると、早い段階で不具合を検出し原因の特定に要する時間を短縮化できるため、開発工数を大幅に削減することが可能です。ぜひお試しください！