初めに
ESP8266 で MQTT を利用する記事はたくさん有ったのですが、TLS を使用して安全に利用する方法はすぐに出てこなかったので、まとめます。
今回は、TLSを利用しますが、クライアント証明書は鍵の管理が面倒なので、利用しません。
環境
MQTT ブローカー
- mosquitto 1.5.8
- ポート 8883 で待ち受け
- Let’s Encrypt の証明書で TLS を有効化済み
- ユーザ/パスワード で認証
ハードウェア
- ESP-WROOM-02
- 共立電子で購入した 4MB 版
- Arduino で使います
Arduino のコード解説
コード自体は最後に乗せています。
// WiFi
#define STASSID "wlanAP"
#define STAPSK "wlanPSK"
// MQTT
#define MQTT_SERVER "mqtt.hoge.fuga"
#define MQTT_PORT 8883
#define MQTT_USER "username"
#define MQTT_PASS "password"
#define MQTT_PUB_TOPIC "topicname"
#define MQTT_SUB_TOPIC "topicname"
#define MQTT_ID_PREFIX "iotdevice-"
// NTP
#define NTP_SERVER_PRIMARY "ntp.nict.jp"
#define NTP_SERVER_SECONDARY "pool.ntp.org"
#define NTP_UTC_OFFSET 60 * 60 * 9
- WIFIの設定など
- MQTTの認証情報など
- MQTT_PUB_TOPIC MQTT_SUB_TOPIC は、利用するトピック名を指定してください。
- MQTT_ID_PREFIX は、この文字列の後ろにMACアドレスを付加した物をMQTTのIDとして利用します。
- NTPの設定など
- 証明書の検証に必要なので、NTPで時刻を合わせます。
- 必要に応じてUTCとの時差やNTPサーバを変更してください。
// CA
// DST_Root_CA_X3.pem
// notBefore=Sep 30 21:12:19 2000 GMT
// notAfter=Sep 30 14:01:15 2021 GMT
static const char ca_cert[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
)EOF";
- Let’s Encrypt のルート証明書です。
- Let’s Encrypt 以外の場合は適宜変えてください。
- 記載のものは2021年までなので、更新が必要な場合があるかもしれません。
void loop()
{
if (!mqtt.connected() && MQTTConnect())
{
// 接続成功
mqtt.subscribe(MQTT_SUB_TOPIC);
MQTTPubStr(MQTT_ID_PREFIX + getMacAddr() + " WakeUP!");
}
mqtt.loop();
}
- 接続時と再接続時に "MQTT_ID_PREFIX MACアドレス WakeUP!" を Publish します。
- MQTTPubStr で文字列を簡単に Publish できます。
// イベントが来た際に呼ばれる
void MQTTHandle(char *topic, byte *payload, unsigned int length)
{
}
- Subscribe しているトピックにメッセージがあった際に呼ばれます
- https://pubsubclient.knolleary.net/api.html#callback 等を参考にしてください。
Arduino のコード
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <time.h>
#include <PubSubClient.h>
// WiFi
#define STASSID "wlanAP"
#define STAPSK "wlanPSK"
// MQTT
#define MQTT_SERVER "mqtt.hoge.fuga"
#define MQTT_PORT 8883
#define MQTT_USER "username"
#define MQTT_PASS "password"
#define MQTT_PUB_TOPIC "topicname"
#define MQTT_SUB_TOPIC "topicname"
#define MQTT_ID_PREFIX "iotdevice-"
// NTP
#define NTP_SERVER_PRIMARY "ntp.nict.jp"
#define NTP_SERVER_SECONDARY "pool.ntp.org"
#define NTP_UTC_OFFSET 60 * 60 * 9
// CA
// DST_Root_CA_X3.pem
// notBefore=Sep 30 21:12:19 2000 GMT
// notAfter=Sep 30 14:01:15 2021 GMT
static const char ca_cert[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
-----END CERTIFICATE-----
)EOF";
BearSSL::WiFiClientSecure wifiClient;
BearSSL::X509List cert(ca_cert);
PubSubClient mqtt(wifiClient);
void setup()
{
Serial.begin(115200);
Serial.println("Booting");
// Wi-Fi接続
WiFi.mode(WIFI_STA);
WiFi.begin(STASSID, STAPSK);
while (WiFi.waitForConnectResult() != WL_CONNECTED)
{
Serial.println("Connection Failed! Rebooting...");
delay(5000);
ESP.restart();
}
Serial.println("Ready");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
// NTPで時刻合わせ
setTimeNTP();
// LetsEncryptの中間証明を読み込む
wifiClient.setTrustAnchors(&cert);
// MQTTの設定
mqtt.setServer(MQTT_SERVER, MQTT_PORT);
mqtt.setCallback(MQTTHandle);
}
void loop()
{
if (!mqtt.connected() && MQTTConnect())
{
// 接続成功
mqtt.subscribe(MQTT_SUB_TOPIC);
MQTTPubStr(MQTT_ID_PREFIX + getMacAddr() + " WakeUP!");
}
mqtt.loop();
}
// MQTTブローカに接続を行う
bool MQTTConnect()
{
Serial.print("Waiting MQTT connection...");
char buf[50];
(MQTT_ID_PREFIX + getMacAddr()).toCharArray(buf, 50);
if (mqtt.connect(buf, MQTT_USER, MQTT_PASS))
{
Serial.println("connected");
return true;
}
else
{
Serial.print("failed, rc=");
Serial.println(mqtt.state());
return false;
}
}
// イベントが来た際に呼ばれる
void MQTTHandle(char *topic, byte *payload, unsigned int length)
{
}
// 文字列を投げる
void MQTTPubStr(String msg)
{
char buf[1024];
msg.toCharArray(buf, 1024);
mqtt.publish(MQTT_PUB_TOPIC, buf);
}
// NTPで時刻合わせ
void setTimeNTP()
{
Serial.print("Waiting NTP...");
configTime(NTP_UTC_OFFSET, 0, NTP_SERVER_PRIMARY, NTP_SERVER_SECONDARY);
// NTPの取得が終わるまで終わるまで待つ
// 0と比較すると起動してからのカウントアップがあるので、ループから抜けてしまう
while (time(NULL) < 365 * 24 * 60 * 60)
{
Serial.print(".");
delay(1000);
}
Serial.println(getTime());
}
// 現在時刻を文字列で得る
String getTime()
{
time_t t = time(NULL);
struct tm timeinfo;
gmtime_r(&t, &timeinfo);
return asctime(&timeinfo);
}
// Macアドレスを文字列で得る
String getMacAddr()
{
byte mac[6];
char buf[50];
WiFi.macAddress(mac);
sprintf(buf, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return String(buf);
}