メタラーまとんがハイソにやらかすようです

東大理系修士卒JTBCエンジニアのハイソサイエティ(上流階級)な日常

ソラコムのボタンとESP32を繋いでソレノイドを演奏してみた!(AWS IoT 1-click / Lambda / AWS IoT / ESP32)

ども!ネットワークスペシャリストのまとんです。

ソラコムのボタンを使って、手元のソレノイドを動かしたい!という人のために、SORACOMボタンとESP32を接続する方法を紹介します。

使用するAWSのサービスはAWS IoT 1-clickと、Lambdaと、AWS IoTです。

ESP32とは?

Arduinoライクに開発できるマイコンです。Arduinoを使ったことがある人なら、同じように使えるので移行が簡単です。

ESP32は、下記の点でArduinoより優れています。

(1)WiFiBlueToothを標準搭載している

Arduinoでこのような無線機能を持たせる場合、追加モジュールを実装しないといけないので、めんどくさいです。

(2)Arduino本家より安い

Arduino Unoは3200円、Arduino microでも2700円のところ、ESP32の開発キット(ESP-WROOM-32)は2180円です。

Arduino以上の機能があって、Arduinoより安い。すごいですね。ESP32を使わない理由がほとんどないです。

ESP32のデメリットとしては、Arduinoよりも統合開発環境(IDE)の提供が不十分であり、環境構築に手間がかかることがありました。

しかし、ESP32の開発スピードは非常に早く、今ではArduino IDEでほぼ全ての機能が使えるようです。なので、もはやESP32のデメリットは無くなったと感じています。

この記事では、ESP32の導入から、ESP32をWiFiに繋ぎ、AWS IoTとの通信を経由して、ソラコムのボタンとの接続を実装します。

作りたいもの

「ボタンを押したらソレノイドが動くおもちゃ」を作りたいです。

AWSの練習を兼ねているので、AWSを介した仕組みを実装します。

f:id:highso:20190310213744p:plain

想定しているアーキテクチャです。

SORACOMのボタン(SORACOM LTE-M Button powered by AWS)を押すと、AWS上でLambdaが走って、AWS IoTにMQTTをPublishします。

ソレノイドを駆動するArduinoは通信機能を持たないので、WiFiモジュールとセットになったESP32で代用し、iPhoneテザリングでインターネットに繋げます。

ESP32にAWS IoTの証明書を保存しておけば、これでセキュアに通信できるはず。

f:id:highso:20190224143744j:plain
f:id:highso:20190224215137j:plain
(左)ソラコムのボタン、(右)ソレノイドとArduino

進捗

f:id:highso:20190527003618p:plain

この記事では、オレンジ色の枠内を実装します。

青枠の箇所が気になる方は、過去の記事をご覧ください。

2019/2/24・・・ソラコムのボタンでAWS IoT 1-clickを起動する

【JAWS DAYS 2019参加レポ】「あのボタン」SORACOMの薄い本を買ってみた! - メタラーまとんがハイソにやらかすようです

2019/2/25・・・Arduinoでソレノイドを駆動する

ソレノイド(ZHO-0420S-05A4.5)をArduinoとMOSFETで動かしてみた! - メタラーまとんがハイソにやらかすようです

2019/3/11・・・Windows上でUbuntuを走らせて、AWS IoTと疎通確認する

AWS IoTをWSL(Windows Subsystem for Linux)のUbuntuで動かしてみた! - メタラーまとんがハイソにやらかすようです

2019/4/32・・・ソラコムのボタンでLambdaを起動して、AWS IoTと疎通確認する

ソラコムのボタンでUbuntuとサーバーレス通信してみた!(AWS IoT 1-click / Lambda / AWS IoT / Windows Subsystem for Linux: WSL) - メタラーまとんがハイソにやらかすようです

参考にさせていただいた記事

Arduino core for the ESP32 のインストール方法 | mgo-tec電子工作

ESP32 Core Board V2 (ESP32 Dev Module)をArduino IDEで使ってみた - Qiita

ESP32〜AWS IoTでMQTT通信して詰んだ話【ClientID】 - Qiita

動作環境

・ESP-WROOM-32 DevKitC V4

waves ESP32 DevKitC V4 ESP-WROOM-32 ESP-32 WiFi BLE 技適取得済 国内発送

waves ESP32 DevKitC V4 ESP-WROOM-32 ESP-32 WiFi BLE 技適取得済 国内発送

 

Windows 10

Arduino IDE 1.8.9

Arduino core for the ESP32 1.0.2

・PubSubClient 2.7.0

・ArduinoJson 6.10.1

Arduino IDEでESP32の開発環境を整える

ESP32の開発では、「コーディング→コンパイル→書き込み」の流れを毎回行います。

これらを、自分の好きなエディタでコーディングし、コマンドラインからコンパイルしてもよいですが、

僕のオススメは、全てArduino IDEでやってしまうことです。

コーディングして、ボタン一個で書き込みして、シリアルモニタも見れるのは、やっぱりお手軽です。

最新版のArduino IDEをインストールする

下記の公式サイトから最新バージョンをダウンロードします。この記事では1.8.9でした。

Arduino - Software

Windowsの場合、オススメはWindows Installer, for Windows XP and up」です。

最近では、Windows10のアプリ版(Windows app)もリリースされているようですが、僕はまだ使ったことがありません。

ボードマネージャからESP32のボードドライバをインストールする

基本的にはこのサイト様を参考にさせていただきました。

Arduino core for the ESP32 のインストール方法 | mgo-tec電子工作

2018年以前の記事では、「GitHubからzipをダウンロードしてきて~」などの説明が散見されますが、現在ではArduino IDE上でドライバのインストールが完結してしまいます。

ESP32の開発スピードの速さに驚かされます。

では、具体的な手順を説明します。

(1) ESP32のドライバ(Arduino core for the ESP32esp)を提供している下記GitHubにアクセス

GitHub - espressif/arduino-esp32: Arduino core for the ESP32

(2) 「Using Arduino IDE Boards Manager (preferred)」のリンク先を開く

(3)  「Enter https://dl.espressif.com/dl/package_esp32_index.json into Additional Board Manager URLs field. You can add multiple URLs, separating them with commas.

」のアドレス部分(https://~ .json)をコピー

(4) Arduino IDEを開き、ファイル->環境設定を開く

(5) 「追加のボードマネージャのURL」の右にあるアイコンをクリックして開き、(3)でコピーしたURLをコピーしてOK

f:id:highso:20190527011323p:plain

(6) ツール->ボード->ボードマネージャを開く

(7) 「esp32」で検索し、「esp32 by Espressif Systems」をインストール。この記事では1.0.2が最新版でした。

f:id:highso:20190527011057p:plain

(8) USBケーブルでPCとESP32を接続。ツール->ボードでESP32が認識されていればOK。(シリアルポートを選ぶ必要があるかもしれません。今回はCOM6でした)

ボードには様々なESP32開発キットの名前がありますが、今回使ったキット(ESP-WROOM-32 DevKitC V4)では「ESP32 Dev Module」で問題ありませんでした。

f:id:highso:20190527011654p:plain

ここで、CPU周波数で、ESP32の最大の240MHzを選択できます。

2018年以前の記事では、「Arduino IDEではESP32の性能をフルに使えない」といった記述が散見されていますが、この実装をした2019年4月時点では、全ての機能をフル活用できるように感じました。

各種必要なライブラリをインストールする

EPS32とAWS IoTをMQTTで通信し、jsonをやり取りするためのライブラリをインストールします。

(1) スケッチ->ライブラリをインクルード->ライブラリを管理

(2) 「pubsubclient」で検索し、「PubSubClient by Nick O'Leary」をインストール。今回は2.7.0。

f:id:highso:20190527012304p:plain

(3) 同じように、「arduinojson」で検索し、「ArduinoJson by Benoit Blanchon」をインストール。今回は6.10.1。

ESP32と自宅のWiFiの接続確認

下記スケッチをESP32に書き込みます。

 #include <WiFiClientSecure.h>

const char* ssid = "XXXXXXXXX"; //your network SSID
const char* pass = "YYYYYYYYY"; //your network password


WiFiClientSecure client;

void setup() {
  // initialize serial:
  Serial.begin(9600);

  // connect to WiFi AP
  Serial.println("Attempting to connect to WPA network...");
  Serial.print("SSID: ");
  Serial.println(ssid);
  WiFi.begin(ssid, pass);
  Serial.print("WiFi connecting");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(100);
  }

  Serial.println(" connected");
  //Serial.print("HTTP Server: http://");
  Serial.print("My IP address: ");
  IPAddress myAddress = WiFi.localIP();
  Serial.print(myAddress);
  Serial.println("/");
}

void loop() {
}

 XXXXXXには自分の家(またはスマホテザリング)のWiFiSSDを、YYYYYYYにはそのパスワードを入れてください。

SSIDには日本語が含まれていてもOKでした。

シリアルポートを開き、うまくWiFiに接続できると、下記のようにIPアドレス(192.168.0.14)が表示されます。

f:id:highso:20190527013910p:plain
このIPは、家庭WiFi内のローカルアドレスです。ESP32が、WiFi内の一つの装置として接続されて、家庭内ルーターDHCPで動的IPが割り振られたということです。

また、このスケッチでは、ESP32はhttpサーバーを立ち上げて待ち受けています。

では、同じWiFi内にある他の装置から、ESP32への疎通チェックを行います。

疎通チェック1:Pingコマンド

PCでコマンドプロンプトを開いて、「ping 192.168.0.14」と打ちます。

192.168.0.14 に ping を送信しています 32 バイトのデータ:
192.168.0.14 からの応答: バイト数 =32 時間 =24ms TTL=255
192.168.0.14 からの応答: バイト数 =32 時間 =142ms TTL=255
192.168.0.14 からの応答: バイト数 =32 時間 =53ms TTL=255

上記のように表示されれば、通信OKです。

ためしに192.168.0.13にpingを打つと、通信しない場合の表示が確認できると思います。

疎通チェック2:ブラウザからアクセス

PCのブラウザから、「http://192.168.0.14」にアクセスします。

Chromeでは下記のように表示されました。これで疎通OKです。

このサイトにアクセスできません

192.168.0.14で接続が拒否されました

これは、ESP32のhttpサーバーにアクセスしたけれど、ESP32側で何も表示するものを用意していないため、このような表示になります。

ためしにhttp://192.168.0.13にアクセスすると、画面が切り替わるまでに時間がかかると思います。そんなアドレスのサーバーは誰もいないため、通信が迷子になって、時間がかかるためです。

AWS IoTの証明書を入手する

ここまでで、ESP32がWiFiに繋がる、つまりインターネットに繋がることが確認できました。

次は、自分のAWSアカウントのAWS IoTとの接続を試みます。

AWS IoTと通信するためには、クライアント側にAWS IoTの証明書3点を入れる必要があります。

ESP32にはファイル構造が無いため、証明書3点の中身をスケッチに平文でベタ書きすることで、ESP32がAWS IoTと通信できるようにします。

(セキュリティの観点から言うと、ESP32の中身をリバースエンジニアリングすれば、証明書を盗まれる可能性もあります。ESP32を物理的に盗まれうる場所に置くユースケースでは、この実装方法はオススメできません。)

AWS IoTの証明書は、AWSコンソールのトップページの下部「ソリューション」にある、

IoT デバイスを接続する
AWS IoT を使用
5分

から、Zipでダウンロードできます。

僕はUbuntu用のものを、Windows Subsystem for Linux (WSL)のUbuntuにダウンロードしました。

詳細は下記記事をご覧ください。

AWS IoTをWSL(Windows Subsystem for Linux)のUbuntuで動かしてみた! - メタラーまとんがハイソにやらかすようです

ダウンロードして展開したものの中から、下記3種が証明書です。

・root-CA.crt

・thingname.cert.pem

・thingname.private.key

これらの中身を見れることを確認してから、次へ。

AWS IoTの設定

エンドポイントのコピー

AWSコンソールからIoT Coreを開き、「設定」からカスタムエンドポイント「~~~~~iot.us-west-2.amazonaws.com」をコピーしておきます。

セキュリティポリシーの設定

IoT Coreの「管理」->「モノ」から、今回使用するモノを開く。

「セキュリティ」から、証明書の一つを開く。(モノの登録時に作った一個だけがあるはず)

「ポリシー」から、アタッチされているポリシーを開く。

ここで、(1) Topic名「HighsoTopic」を許可する、(2)クライアント名「ESP32」を許可するポリシードキュメントを書きます。

具体的には下記を書きました。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Publish",
        "iot:Receive"
      ],
      "Resource": [
        "arn:aws:iot:us-west-2:0123456789:topic/*",
        "arn:aws:iot:us-west-2:0123456789:topic/sdk/test/java",
        "arn:aws:iot:us-west-2:0123456789:topic/sdk/test/Python",
        "arn:aws:iot:us-west-2:0123456789:topic/topic_1",
        "arn:aws:iot:us-west-2:0123456789:topic/topic_2"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
      "iot:Subscribe"
    ],
    "Resource": [
      "arn:aws:iot:us-west-2:0123456789:topicfilter/*",
      "arn:aws:iot:us-west-2:0123456789:topicfilter/sdk/test/java",
      "arn:aws:iot:us-west-2:0123456789:topicfilter/sdk/test/Python",
      "arn:aws:iot:us-west-2:0123456789:topicfilter/topic_1",
      "arn:aws:iot:us-west-2:0123456789:topicfilter/topic_2"
    ]
    },
    {
      "Effect": "Allow",
      "Action": [
      "iot:Connect"
    ],
      "Resource": [
        "arn:aws:iot:us-west-2:0123456789:client/ESP32",
        "arn:aws:iot:us-west-2:0123456789:client/sdk-java",
        "arn:aws:iot:us-west-2:0123456789:client/basicPubSub",
        "arn:aws:iot:us-west-2:0123456789:client/sdk-nodejs-*"
      ]
    }
  ]
}

クライアント名「ESP32」は別の名前でもよいのですが、これを許可しておかないと通信できません。

詳細は下記サイト様を参考にしました。

ESP32〜AWS IoTでMQTT通信して詰んだ話【ClientID】 - Qiita

AWS IoTとMQTT通信するスケッチ

下記は「ソラコムのボタンを押したら、ソレノイドがUnder The Seaを演奏し始める」スケッチです。

#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

 

const char* ssid = "XXXXXXXX"; //your network SSID
const char* pass = "YYYYYYYY"; //your network password

const char* server = "ZZZZZZZZ.iot.us-west-2.amazonaws.com"; //end point
const int port = 8883;

 

WiFiClientSecure client;
PubSubClient mqttClient(client);

long messageSentAt = 0;
int Value = 0;
char pubMessage[128];
char* inTopic = "HighsoTopic";

 

// Static buffer for JSON
StaticJsonDocument<200> doc;
char json[] = "{"message":"Initial message"}";

 

//Solenoid configuration
int TR = 27; //Output Pin
int note = 150; //Tempo
int claptime = 50;
int rest = note - claptime;
int flag = -1;

 

const char* Root_CA =
"-----BEGIN CERTIFICATE----- "
"******************* "
"******************* "
"******************* "
"-----END CERTIFICATE----- ";

 

const char* Client_cert =
"-----BEGIN CERTIFICATE----- "
"******************* "
"******************* "
"******************* "
"-----END CERTIFICATE----- ";

 

const char* Client_private =
"-----BEGIN RSA PRIVATE KEY----- "
"******************* "
"******************* "
"******************* "
"-----END RSA PRIVATE KEY----- ";

 

void connectAWSIoT() {
  Serial.print("AWS IoT connecting...");
  while (!mqttClient.connected()) {
    if (mqttClient.connect("ESP32")) { //ClientID
      Serial.println(" connected.");
    } else {
      Serial.print("Failed. Error state=");
      Serial.print(mqttClient.state());
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

 

void mqttSubscribe(char* TopicName){
  int qos = 0;
  Serial.print("Subscribing. topic: ");
  Serial.print(TopicName);
  Serial.print("...");
  while(!mqttClient.subscribe(TopicName, qos)){
    Serial.print(".");
    delay(100);
  }
  Serial.println(" succeess.");
}

 

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

  // Callback function for subscribe
  Serial.print("Received. topic: ");
  Serial.println(topic);
  for (int i = 0; i < length; i++) {

    Serial.print( (char)payload[i] );

    json[i] = (char)payload[i];

  }

  Serial.print(" ");

  // Deserialize the JSON document

  deserializeJson(doc, json);

  const char* message = doc["message"];

  Serial.print("Parsed message: ");

  Serial.println(message);

  // Action

  flag = flag * -1;

}

 

void clap() {
  digitalWrite(TR,HIGH);
  delay(claptime);
  digitalWrite(TR,LOW);
  delay(rest);
}

 

void underTheSea() {
  clap();
  clap();
  clap();
  clap();

  delay(note);
  clap();
  delay(note);
  clap();

  clap();
  delay(note);
  clap();
  delay(note);

  clap();
  delay(note);
  clap();
  delay(note);


  clap();
  clap();
  clap();
  clap();

  delay(note);
  clap();
  delay(note);
  clap();

  clap();
  delay(note);
  clap();
  delay(note);

  clap();
  delay(note);
  clap();
  delay(note);

  delay(note);
  delay(note);
  delay(note);
  delay(note);
}

 

void setup() {
  // initialize serial:
  Serial.begin(9600);

  // connect to WiFi AP
  Serial.println("Attempting to connect to WPA network...");
  Serial.print("SSID: ");
  Serial.println(ssid);
  WiFi.begin(ssid, pass);
  Serial.print("WiFi connecting");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(100);
  }


  Serial.println(" connected");
  //Serial.print("HTTP Server: http://");
  Serial.print("My IP address: ");
  IPAddress myAddress = WiFi.localIP();
  Serial.print(myAddress);
  Serial.println("/");

 

  // connect to AWS IoT
  client.setCACert(Root_CA);
  client.setCertificate(Client_cert);
  client.setPrivateKey(Client_private);
  mqttClient.setServer(server, port);
  mqttClient.setCallback(mqttCallback);

  connectAWSIoT();

  mqttSubscribe(inTopic);

  pinMode(TR,OUTPUT);
}

 

void loop() {
  if (!mqttClient.connected()) {
    Serial.println("Disconnected.");
    connectAWSIoT();
    mqttSubscribe(inTopic);
  }
  mqttClient.loop();

  if(flag == 1){
    underTheSea();
  }
}

XXXXXとYYYYYにはWifiSSIDとパスワードを記入してください。

ZZZZZZにはAWS IoTのエンドポイントをコピーしてください。

Root_CAなどの*******************には、証明書3点の中身を平文でコピーしてください。

ソレノイドを駆動するMOSEFTのゲートは27ピンに接続してください。今回使用する回路図は下記の通りです。

f:id:highso:20190224214731p:plain

回路図の仕組みは下記の記事を参考にしてください。

ソレノイド(ZHO-0420S-05A4.5)をArduinoとMOSFETで動かしてみた! - メタラーまとんがハイソにやらかすようです

このスケッチでは、setup()でWiFiAWS IoTに接続し、トピック名HighsoTopicをSubscribeします。

loop()のmqttClient.loop();で待ち受け状態になります。

何かをSubscribeすると、コールバック関数であるmqttCallback()が立ち上がります。受け取ったJSONを変換してprintしていますが、今回はメッセージ内容に関係なく、何かを受け取ったらアクションとしてflagを1/-1に切り替えています。

flagは最初は-1で、何かsubscribeして1になったとき、underTheSea()でソレノイドを演奏させます。

Lambdaの設定

ソラコムボタンを押した時に、Lambdaを起動してAWS IoTにpublishするようにします。

AWSコンソールからLambdaを開き、AWSIoTFullAccessを持ったロールで、出力をAWS IoTにして、下記Pythonスクリプトを登録しておきます。

# coding: utf-8


#ライブラリのimport
import json
import boto3

#Functionの開始を出力
print('Loading function')

#AWS IoT Data Planeオブジェクトを取得
iot = boto3.client('iot-data')

#Lambdaのメイン関数
def lambda_handler(event, context):

## TODO implement
#return {
# 'statusCode': 200,
# 'body': json.dumps('Hello from Lambda!')
#}

#トピックを指定
topic = 'HighsoTopic'
#メッセージの内容
payload = {
  "message": "Hello from Lambda"
}

try:
#メッセージをPublish
iot.publish(
  topic=topic,
  qos=0,
  payload=json.dumps(payload, ensure_ascii=False)
)

return "Succeeeded."

except Exception as e:
print(e)
return "Failed."

Topic名「HighsoTopic」に、payloadに格納したJSON("message": "Hello from Lambda")を送信しています。

このLambda関数を、AWS 1-clickを開いて、プロジェクトの動作として紐づけます。

Lambdaの詳細については、下記記事をご覧ください。

ソラコムのボタンでUbuntuとサーバーレス通信してみた!(AWS IoT 1-click / Lambda / AWS IoT / Windows Subsystem for Linux: WSL) - メタラーまとんがハイソにやらかすようです

動作結果

f:id:highso:20190527030014j:image

やっと完成しました!!

機能の考察

ボタンを押してからソレノイドが動き始めるまで、10秒ほどのタイムラグがあります。

ラグのほとんどは、ソラコムのボタンが緑に点灯するまで(LTEに繋がってAWS 1-clickを誘発するまで)にかかっており、その後のAWS IoT~ESP32のラグはほぼ無いように感じました。

拡張性の考察

今回は「ESP32がsubscribeしたらflagを1/-1に切り替えるアクション」を実装しました。

アクションの内容を、自分の所望のものに書き換えれば、色々と面白いことができそうです。

また、ソラコムボタンは「1回押し、2回押し、長押し」の3通りを使えますが、今回は区別していません。Lambdaで送るpayloadにこれらのメッセージを載せれば、ESP32が区別できるようになるはずです。

この記事のまとめ

  • Arduino IDEでESP32の環境構築
  • ESP32を自宅WiFiに繋いで接続確認
  • AWS IoT Coreから証明書の入手、アクセスポイントの入手、ポリシーの設定
  • ESP32で、AWS IoTと通信して、Topic名「HighsoTopic」をsubscribeするたびにflagを1/-1反転し、ソレノイドで曲を演奏するスケッチ
  • ソラコムのボタンからLambdaでHightoTopicにPublishすうるPythonスクリプト

今回の実装を通して、様々なテクニックを身に付けました。

AWSはほぼ初めて使ったし、ESP32デビューも果たせました。

最後に、次の記事で、今回使ったスクリプト群をGitHubにアップロードして、GitHubデビューもしようと思います。

はてなブログソースコードに色付けができないので、ソースの共有に向いていないですね)

そして僕は、ソリューションアーキテクトを目指します。

 

以上、メタラーまとんでした。

ではでは。

今回使ったデバイス

SORACOM LTE-M Button powered by AWS

SORACOM LTE-M Button powered by AWS

 
waves ESP32 DevKitC V4 ESP-WROOM-32 ESP-32 WiFi BLE 技適取得済 国内発送

waves ESP32 DevKitC V4 ESP-WROOM-32 ESP-32 WiFi BLE 技適取得済 国内発送

 

ソラコムボタンで遊んでみた記事まとめ

1. 【JAWS DAYS 2019参加レポ】「あのボタン」SORACOMの薄い本を買ってみた!

2. ソレノイド(ZHO-0420S-05A4.5)をArduinoとMOSFETで動かしてみた!

3. AWS IoTをWSL(Windows Subsystem for Linux)のUbuntuで動かしてみた!

4. ソラコムのボタンでUbuntuとサーバーレス通信してみた!(AWS IoT 1-click / Lambda / AWS IoT / Windows Subsystem for Linux: WSL)

5. ソラコムのボタンとESP32を繋いでソレノイドを演奏してみた!(AWS IoT 1-click / Lambda / AWS IoT / ESP32)