kghr IT備忘録
HOME » Category : Arduino HOME » BackNumber
エントリー目次
ESP32にWiFi接続したら自動的にトップページを開くみたいな
2020.08/13 (Thu)

空港やホテルでフリーWi-Fiに接続した際に、すぐにはインターネットには繋がらず同意画面などのページが表示されるような仕組みをESP32で試してみた。
完成品
先に動くようになったコードを書いておく。
ESP32で立てたアクセスポイント”ESP32LED”にWiFi接続する。この例で立てたアクセスポイントにはパスワードを設定していないので、WiFi接続するとLEDをON/OFFするページが自動的に表示される。
Webの画面でONやOFFを押すとLED(LED_BUILTIN)が点滅する仕組みをやりたかったのではなく、WiFiに接続すると自動的に操作用の画面に切り替わるということをしたかったのだ。
#include <WiFi.h>
#include <DNSServer.h>
#include <WebServer.h>
//#define LED_BUILTIN 2;
IPAddress apIP(192,168,1,100);
WebServer webServer(80);
const char* ap_ssid = "ESP32LED"; //APのSSID
DNSServer dnsServer;
void LED_top() {
String html = "";
html +="<html><body>";
html +="LED [<a href='/on'>ON</a>] [<a href='/off'>off</a>]";
html +="</body></html>";
webServer.send(200, "text/html", html);
}
void LED_on() {
digitalWrite(LED_BUILTIN, HIGH);
// 「/」に転送
webServer.sendHeader("Location", String("/"), true);
webServer.send(302, "text/plain", "");
}
void LED_off() {
digitalWrite(LED_BUILTIN, LOW);
// 「/」に転送
webServer.sendHeader("Location", String("/"), true);
webServer.send(302, "text/plain", "");
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
WiFi.mode(WIFI_AP);
WiFi.softAP(ap_ssid); // no password
delay(200); // Important! This delay is necessary
WiFi.softAPConfig(apIP,apIP,IPAddress(255,255,255,0));
dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
dnsServer.start(53, "*", apIP);
webServer.on("/", LED_top);
webServer.on("/on", HTTP_GET, LED_on);
webServer.on("/off", HTTP_GET, LED_off);
webServer.onNotFound(LED_top);
webServer.begin();
}
void loop() {
dnsServer.processNextRequest();
webServer.handleClient();
}
つまづいたところ
サンプルを参考にプログラムしたが、接続後にPanicが発生しESP32がリセットを繰り返してしまうという現象が多発。サンプルのままなのにどこが悪いのだろうか。
dhcps: send_offer>>udp_sendto result 0
Guru Meditation Error: Core 0 panic'ed (InstrFetchProhibited). Exception was unhandled.
Core 0 register dump:
PC : 0x00000000 PS : 0x00060e30 A0 : 0x80114061 A1 : 0x3ffb3b40
A2 : 0x3ffcda3c A3 : 0x3ffcccf8 A4 : 0x3ffcd168 A5 : 0x3ffcd148
A6 : 0x6501a8c0 A7 : 0x6f01a8c0 A8 : 0x80113f04 A9 : 0x3ffb3b00
A10 : 0x3ffcda4c A11 : 0x3ffcccf8 A12 : 0x3ffb3b4c A13 : 0x00000044
A14 : 0x00000001 A15 : 0x00000006 SAR : 0x00000010 EXCCAUSE: 0x00000014
EXCVADDR: 0x00000000 LBEG : 0x4000c349 LEND : 0x4000c36b LCOUNT : 0x00000000
Backtrace: 0x00000000:0x3ffb3b40 0x4011405e:0x3ffb3b80 0x40120ee5:0x3ffb3ba0 0x40125eed:0x3ffb3be0 0x4012b18a:0x3ffb3c00 0x40114a67:0x3ffb3c20 0x400889ad:0x3ffb3c50
Rebooting...
デバッグのメッセージがヒントになるかもしれないと、"Core Debug Level"を”なし”から”Debug"に変更したら、プログラムを変えてないのに何事もなかったように動く。きっとタイミング的な要素があるに違いない。

解決したところ
結局、softAP()とsoftAPConfig()の間にdelay(100)を入れることで解決した。念のためdelay(200)にしておく。
OKパターン
WiFi.softAP(ap_ssid); // no password
delay(200); // Important! This delay is necessary
WiFi.softAPConfig(apIP,apIP,IPAddress(255,255,255,0));
いくつか試したうち、NGパターンも書いておく。
NGパターン①
softAP()とsoftAPCofig()の間にdelay()を入れないと、panicになってしまう。
WiFi.softAP(ap_ssid); // no password
WiFi.softAPConfig(apIP,apIP,IPAddress(255,255,255,0));
NGパターン②
softAP()より先にsoftAPCofig()を配置するとpanicになってしまう。
WiFi.softAPConfig(apIP,apIP,IPAddress(255,255,255,0));
WiFi.softAP(ap_ssid); // no password
NGパターン③
softAP()より先にsoftAPCofig()を配置してdelay()を入れてもpanicになってしまう。
WiFi.softAPConfig(apIP,apIP,IPAddress(255,255,255,0));
delay(200);
WiFi.softAP(ap_ssid); // no password
まとめ
海外のサイトにも、このpanicになる件に関して書き込みを見かけたが、どれも解決に至っていなかった。「自分はこれで出来てるけど?」的な書き込みがあっても出来ない人は出来ない。
きっとESP32の個体差とかバージョンの違いがあるに違いないのでここに書いたとおりにプログラムしても動かないこともあるだろう。
ちなみに冒頭の写真のESP32は”MH ET LIVE ESP32MiniKit”だが、手持ちの"DOIT ESP32 DEVKIT V1"でも動作した。
とりあえずここでは動くようになったから良しとする。
今日はここまで。
スポンサーサイト
ESP32でWS2812Bを使うときはFastLEDライブラリで
2020.06/24 (Wed)

以前作ったWordClockをESP32の開発キットで作り直そうとしたら、LEDがうまく点灯しなかったので実験した備忘録。
結論から先に書くと、ESP32でAdafruit_NeoPixelを使ったら不安定だったのでFastLEDのライブラリを使ったというもの。前回はESP-WROOM-02でFastLEDのライブラリを使ったら不安定だったのでAdafruit_NeoPixelを使ったという経緯があったが、今回はその逆だ。
テープLEDはWS2812B
テープLEDは電源と信号線を数本配線するだけで、数十から数百のLEDをコントロールできるのでとても便利。WS2812Bはマイコンチップを搭載したシリアルLED。直列に繋いだLEDを信号線一本で制御できる。
LED単体の最大電流値は約50mAということなので、USBから電源をとったりESP32と電源を共有したりするときには、要注意。大量に点灯するときには明るさを抑えるなどしないと、大量の電流が流れて発熱したり壊れたりとか。
LED点灯テストのスケッチ
Adafruit_NeoPixelライブラリの場合
↓テストに使用したライブラリ

↓スケッチ
// Adafruit_NeoPixel simple sketch
#include <Adafruit_NeoPixel.h>
#define LED_PIN 16
#define NUM_LEDS 100
#define BRIGHTNESS 20
Adafruit_NeoPixel pixels(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
#define DELAYVAL 100 // Time (in milliseconds) to pause between pixels
void setup() {
pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
pixels.setBrightness(BRIGHTNESS);
}
void loop() {
pixels.clear(); // Set all pixel colors to 'off'
for(int i=0; i<NUM_LEDS; i++) { // For each pixel...
pixels.setPixelColor(i, pixels.Color(0, 255, 0));
//portDISABLE_INTERRUPTS();
pixels.show();
//portENABLE_INTERRUPTS();
delay(DELAYVAL); // Pause before next pass through loop
}
FastLEDライブラリの場合
↓テストに使用したライブラリ

↓スケッチ
// FastLED simple sketch
#include <FastLED.h>
#define LED_PIN 16
#define NUM_LEDS 100
#define BRIGHTNESS 20
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];
#define DELAYVAL 100 // Time (in milliseconds) to pause between pixels
void setup() {
delay( 3000 ); // power-up safety delay
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
FastLED.setBrightness(BRIGHTNESS);
}
void loop()
{
FastLED.clear(); // clear all pixel data
for(int i=0; i<NUM_LEDS; i++) { // For each pixel...
leds[i] = CRGB( 0, 255, 0);
FastLED.show();
delay(DELAYVAL); // Pause before next pass through loop
}
}
LED点灯してみた様子
Adafruit_NeoPixelライブラリの場合
まだ点灯指示をしていないのに、ところどころLEDが点灯してしまっている。show()の処理に1mSec以上かかると、データ送出の最中にインタラプトが入ってフォーマットが崩れてしまうことが原因だということらしい。(参考元)
LEDを点灯するshow()命令の前後に、割り込み禁止のコードportDISABLE_INTERRUPTS() を書いても、点灯する場所が変わるだけで根本的な解決にはならないようだ。
FaseLEDライブラリの場合
特に問題なく点灯している。前回ESP-WROOM-02でFastLEDのライブラリを使ったときは全体にチラチラ点滅してしまうような挙動だったが、今回はそのようなことはない。
まとめ
ESP32でテープLEDを使うときは、今のところFastLEDを使えばいいってことのようだ。この先ライブラリのバージョンアップなどあればFastLEDでなくてもよさそうだが、いずれにしても使えればいいか。
ちなみにArduino Nanoでは、Adafruit_NeoPixelライブラリでも問題なく点灯した。
今日はここまで。
【はんだ付け不要】人感センサーお知らせ装置を試作してみた
2020.05/06 (Wed)

人感センサーで人の出入りを記録して、一定条件で通知するIoTデバイスを作ってみた備忘録。うまいことパッケージ化すればそれなりの需要はありそうな気がするのだけれど。
人感センサーお知らせ装置の用途
旅行中の自宅、無人の倉庫、閑散期の別荘、週末の事務所など、人の出入りをとらえたいというニーズはあるだろう。防犯を目的とするなら監視カメラを取り付ければいいのだけれど、日常使い向けにいかにもっとさりげないものを作ってみた。
子供が帰ってきたかな
両親は会社にいながら子供が学校から帰ってきたり、リビングにいるかなどの様子がわかりそう。じっとテレビを見てたりすると動きがなくなるかもしれないが、あまり細かくないさりげなさを特徴にしたい。

定期巡回や常駐の記録
定期的に巡回したり検針するようなニーズがあれば巡回記録になるし、24時間常駐するお仕事の記録もできそうだ。

離れて暮らす高齢の両親をさりげなく見守る
人感センサーが一定時間検知しなかったりしたら、メールやLINEで通知したりすることもできる。通知が来たら電話してみるなどして安否が確認できるのでは。

人感センサー お知らせ装置の準備
人感センサーお知らせ装置の材料
パーツはAmazonで手に入る。いずれも単品で買うと割高なので、複数個まとめて買ったほうが良さそう。モノによっては納期が1カ月くらいのものがあるので注意して選ぶといい。
① ESP32開発キット
ESP32は技適マークが付いているものを選ぶ。似たようなものでESP01とか最近出てきたESP12があるが、現時点では技適マークが付いてないから日本国内では電波法にひっかかる。
② 人感センサー
「焦電型 赤外線検出器 PIRモーション モーションセンサー 人感センサー」などのキーワードで見つかるはず。
検出範囲は、テーパ角度120°以内で7メートル以内となっている。
③ ジャンパーワイヤ(メス-メス)
お安いものからお高いものまでさまざま。お安いものは当たりはずれがあるので、動かなかったら配線を取るかえてみると動くこともあり。また、納期もまちまちなので注意。
ThingSpeakの登録
人感センサーがカウントした数を記録する場所として、ThingSpeakを使う。
ThingSpeak はクラウドの IoTプラットフォームで、データを収集および保存したり、IoT アプリケーションの開発ができる。このサービスは単にグラフ化するだけでなく、閾値を超えたらメールするとか一定期間更新がないときにアクションを起こすなどができ、さらにIFTTTなどにつなげばより込み入ったことができる。
こちらはライセンス体系が明確で、個人利用であれば一定の範囲で無料の利用が可能。1分に1回データを送るくらいであれば安心して使える。
このサービスに登録してチャネルを1つ設定し、① Channel_ID ② Write API Key の2つを入手しておく。登録の仕方は ここのサイトがわかりやすいと思う。
http://iwathi3.hatenablog.com/entry/Data-to-Graph-ThingSpeak
人感センサーお知らせ装置の配線
冒頭の配線図のとおり、ESP32開発キットと人感センサーをジャンパーワイヤで配線するだけ。人感センサーの電源はESP32開発ボードのVIN(5V)からとる。人感センサーのOUTはESP32開発ボードGPIO13につないだ。
手元にあるESP32開発キット(DOIT DEVKIT V1)の場合実際の配線を写真でみたらこんな感じ。

※開発キットによってピンの配置が異なる。例えばKeeYees ESP32 ESP-32S 開発ボードの場合はこんな感じで、上の写真とはピンの位置が違うから注意。

人感センサーの調整
人感センサーにはボリュームが2つと、ジャンパーが1つ付いていて、感度や時間などを調整することができる。
感度調整ボリューム: 赤外線検知感度、検知距離を調整する(右回し感度高)
延長時間調整ボリューム:検知してから保持する時間を調整する(右回し時間長)
モード選択ジャンパー:ショートプラグの挿入位置で再検知有り(H)と再検知無し(L)を設定可能
今回は① 感度最高にして、② 保持時間は最短 ③ 検知エリア内に対象物がある間は検知出力が継続される再検知有り(H)に設定する。実際の写真は以下のような感じ。

人感センサーお知らせ装置の組み立て
準備が整ったので、ESP32にプログラムを書き込んで、ThingSpeakにデータが送信されることを確認する。ThingSpeakが無料で使える範囲にするため、1分間に人感センサーが検知していた回数数え、1分おきに送信することにした。
人感センサーのチェックは1秒ごとに行い、チェックしたときに検知していれば1回とカウントする。
ESP32開発キットへの書き込み
ESP32へのスケッチの書き込み方法はここでは書かないが、Arduino IDEでプログラムを書き込む際のボード選択は、KeeYees ESP32 ESP-32S 開発ボードの場合「ESP32 Dev Module」で良いそうだ。
スケッチの中に① WiFiのパラメータ ②ThingSpeakのパラメータ を設定する箇所があり、ここは利用者の環境にあわせたものを書き込んで使う。
スケッチ(ソースコード)
#include <WiFi.h>
#include <ThingSpeak.h> // https://thingspeak.com/
//define your wifi access point here
#define WIFI_SSID "your wifi ssid code"
#define WIFI_PASS "your ssid password"
//define your ThingSpeak keys here
#define Channel_ID 9999999
#define Write_API_Key "YOUR_WRITEAPIKEY"
//define your device pin
#define Sensor_Pin 13
#define LED_Pin 2
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.println();
// initialize digital pin sensorPin as an input.
pinMode(Sensor_Pin, INPUT);
// initialize digital pin ledPin as an output.
pinMode(LED_Pin, OUTPUT);
digitalWrite(LED_Pin, HIGH);
// We start by connecting to a WiFi network
Serial.println();
Serial.println();
Serial.print("Connecting to ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
digitalWrite(LED_Pin, LOW);
}
void loop() {
int count = 0;
for (int i=0; i < 60; i++){
if(digitalRead(Sensor_Pin)) {
count++;
Serial.print("count: ");
Serial.println(count);
digitalWrite(LED_Pin,HIGH);
delay(1000); // delay a second
digitalWrite(LED_Pin,LOW);
} else {
delay(1000); // delay a second
}
}
if (count > 0) httpRequest(count);
}
void httpRequest(int field1Data) {
// Initialize ThingSpeak
WiFiClient client;
ThingSpeak.begin(client);
// Set the fields with the values
ThingSpeak.setField(1, field1Data);
// Write to the ThingSpeak channel
int res = ThingSpeak.writeFields(Channel_ID, Write_API_Key);
if (res == 200) {
Serial.println("Channel update success.");
} else{
Serial.print("Channel update error: ");
Serial.println(res);
}
client.stop();
}
動作の確認
最終的にはThingSpeakのサイトでグラフデータが出力されていれば問題ない。しかしグラフが更新されないようならチェックが必要。
一応、電源投入後からWiFiに接続完了するまでの間はLEDを点灯しているのでWiFiがつながったかどうかは分かるようにはしてあるが、Arduino IDEのシリアルモニターを開いて電源投入直後からの動作状態を確認したほうが確実だ。
① 起動直後にWiFiに接続できているか

② 1分後にThingSpeakにデータ送信ができているか

③ ThingSpeakのサイトでグラフ化されたか

ThingSpeakの通知設定
設定のガイドは日本語で用意されているので、これを参考に設定するといい。
https://jp.mathworks.com/help/thingspeak/react-app.html
ThingSpeakの通知方法に「ThingHTTP」を使えば、IFTTTに連携してよりLINEやSlackなど多彩なサービスに連携することが簡単にできる。とりあえずここでは、24時間以内にデータ更新がない場合にツイートするという設定をしてみる。
まずツイッターのアカウントを連携してから、Apps→Reactの設定をする。
① [Apps] 、 [React] を選択
② [New React] をクリック
③ 以下を選択
Condition Type: No data check (データを受信しているかどうかをチェック)
Test Frequency: Every 60 minutes (時計上の 1 時間ごとにテスト)
Condition: (テストする条件)
if channel: xxxxxx (人感センサーのチャネル)
has not been update for : 1440 (24時間を分指定)
Action: ThingsTweet (ツイート)
Then Tweet: (ツイートの内容)
Using Twitter account: xxxxx (ツイートに使うアカウント名)
Options: 1度きりか、何度もチェックするか (何度も繰り返さないほうがいいかも)
③ [Save React] をクリック
画面キャプチャしたのは以下のような感じ。

まとめ
Wifiの設定をスマートフォンから簡単にできるようにスケッチを拡張して、電源入れればすぐ使えます的にパッケージすれば需要があるのではないだろうか。うまく行けそうならもう少し膨らませてみてもいいかもしれない。
今回は赤外線方式の人感センサーを使ったが、マイクロ波レーダーセンサー方式のほうがお安くて小型化ができそう。いずれこちらも試してみることにする。
今日はここまで。
ディスプレイの点滅をCdSで受信するシリアル通信をしてみた
2020.03/04 (Wed)

スマートフォンで選択したパラメータをArduinoに送りるとき、画面の点滅で通信できたらいいのにな、と思ってつらつら検索していたら、ちょうどいいものがあったので実験してみた備忘録。
スマートフォンとArduino間の通信手段
WIFI/Bluetooth
ESP32はWiFiやBluetoothが使えるのでうまいこと通信できる。スマートフォンで作りこめばなかなか良さそうなものが作れるだろう。しかし、今回使おうとしているのはArduino Nanoなのでこのような電波的な手段は使えない。
シリアルポート
スマートホンがAndroidだったりPCであれば、シリアルケーブルでつないで設定することもできるが、今どきシリアルケーブルを持っているひとも少ないだろう。
イヤホンジャック、音
音を使うのはいいと思ったのだが、イヤホンジャックなしのiPhoneが出たので使いにくくなった。SoftModemのライブラリを使えばなかなかいい線いけると思うが、ケーブルも必要だしスマートフォンアプリの開発が面倒かなと。
光の点滅を使ったシリアル通信
弘前大学 教育学部のサイトに、「シリアル通信教材(光通信)」というシリアル通信の仕組みを理解するための教材があった。とても参考になる資料で、これが今回探していたものだ。
送信側の光の点滅をスマートフォンのディスプレイで行うことで、Arduinoにデータ信号を送ることができる。
Arduino側、シリアル信号の受信プログラム
この教材ではtinyBasicで作られていたので、Arduino NanoのIDE版に変えて実験してみた。
、
// スマートフォンディスプレイの点滅を受信するプログラム
int T=1000/10; //通信速度 1秒あたり10ビット
int A=600; //明るさの境界値はディスプレイの明るさによる
int B=0; //受信ビット変数
int C=0; //受信文字変数
int sensorPin = A6; // cdsのアナログポート
void setup() {
Serial.begin(115200);
}
void loop() {
while ( analogRead(sensorPin)>=A) { //スタートビットを待つ
delay(1); // 念のためdelayを入れた
}
delay(T/2); // T/2 ms待つ
if (analogRead(sensorPin)>=A) {
Serial.println("Error"); //スタートビット(0)でなければエラー
return;
}
int C=0; // 受信文字Cを初期化
delay(T); // T秒待つ
for (int i=0; i<=7; i++) { //ビット0~ビット7まで
if (analogRead(sensorPin)>=A) B=1;
else B=0; //明るさに応じてBを0または1
Serial.print(B); //受信ビットBを表示
C=C/2+B*128; //Cを右にシフトしビット7をBにし
delay(T); //T秒待って次のビットへ
}
if (analogRead(sensorPin) Serial.println("Error"); //ストップビット(1)でなければエラー
return;
}
Serial.print(":");
Serial.println(C); //受信文字Cを表示し、再び受信待機
delay(100);
}
スマートフォン側:データ送信プログラム
こちらは弘前大学 教育学部のサイト「シリアル通信教材(光通信)」にあるものをそのまま使わせていただいた。
配線
冒頭の画像のとおりだが、メモとして再掲。
光センサー(CdSセル)は、暗いところにおくとセンサー自体の抵抗値が大きくなり、明るいところに置くと抵抗値が小さくなる性質を持っている。
光センサーは抵抗器と同じくプラスマイナスの向きはないのでそのまま接続できるが、明るいときに抵抗値が小さくなって電流が流れ過ぎてしまうため、ここでは10kΩの抵抗をGNDにつないでおく。

実際の動作
この実験では通信速度を10bps = 1秒間に10ビットとし、「0123456789」 の文字を送ってみた。文字コードとして、48から57まできっちり受信できている。
あとがき
なかなか興味深い実験であった。
Cdsを複数使えば点滅の閾値を補正したり、読み取り精度が向上するのではないか。
また、エラー訂正の仕組みや通信速度の調整などができると、使い勝手の良い通信手段になりそう。
今日はここまで。
ESP32でBLEタグを検出してAWS IoTに送ってみた
2019.09/09 (Mon)

デパートや遊園地などで迷子になるのは子供ばかりではない。スマートホンを持っていれば「いまどこ?」的なやりとりで落ち合えるだろうが、全員が持ってない場合はそうもいかない。そこで、ESP32とBLE タグを使って安価な位置検出を試してみる。
設置したESP32は定期的にBLEタグを検出しサーバーにタグの情報をUpする。屋内であろうと屋外であろうと数メートルから数十メートル間隔でESP32を配置しておけば、そのBLEタグがどこにあるかおよそ推定できるだろう。
BLEキーファインダーのネットワーク版といったところだろうか。
まだ実地の試験ができていないがここまでの備忘録をまとめた。
とりあえずやりたいこと
①ESP32でBLE(Bluetooth Low Energy)信号を検出し
②検出したBLEのMACアドレスとRSSI(Received Signal Strength Indicator:受信信号強度)を
③MQTTでAWS IoTに送信し
④AWSのS3に保管するなりLambdaで処理するなどして
⑤位置情報を活用する

実験材料
BLEタグ
Amazonで「Bluetooth ファインダー」などと検索するといろいろ出てくる。技適などちゃんとしていそうな製品はそれなりのお値段がするようだが、とりあえずBluetoothの信号を出してくれればいいので、今回の実験では100円ショップなのに300円で売っているBluetoothのシャッターボタンを使った。これはちゃんと技適マークが付いているから日本国内で使っても問題ないだろう。

ESP32
手元にあった 「MH-ET LIVE D1ミニESP32」を使った。技適の通ったESP32チップを使っているならほかの製品でも良い。
解決した課題
先に、発生した課題とそれを解決した方法を書いておく。
①ESP32でWiFiとBluetoothを同時に使うとメモリが足りない
Arduino IDEの「ツール」メニューで、「Partition Scheme -> No OTA」(もしくはLarge APPやHuge APP)を選択する。
OTA(Over The Air)はESP32にスケッチを書き込む際にUSBケーブルを繋がずとも、WiFi経由書き込みができる機能。USBケーブルを繋げばいいだけのことなので、この機能に使っている領域をプログラムに割り当てる。

②ESP32でHTTPS通信とBluetoothを同時に使うと落ちる
AWS IoT からのすべてのトラフィックは 暗号化する必要がある。ESP32も暗号化のためのプログラムができるのだが、BLEスキャンとWiFi経由でのHTTPS送信を同時に実行するとESP32がリセットして再起動する。btStop()やWiFi.mode(WIFI_OFF)など試してみたもののどうもうまく動作してくれない。
結局、BLEスキャンとWiFi経由でのHTTPS通信を同時に実行しなければいいので、Mosquitto MQTT BrokerをブリッジにしてAWS IoTを使うことにした。ESP32とMosquittoの間はHTTPで通信し、MosquittoとAWS IoTの間はHTTPSで通信する。

AWS IoTの設定をする
AWS IoTの設定部分は以下のサイトを参考にさせていただいた。
#今回はAWSのEC2でMosquittoを動かしたので、同サイトの後半にあるMosquittoのインストールはSkipした。
参考) AWS IoTにMosquittoをブリッジにしてつなぐ
https://qiita.com/poruruba/items/084317e01b9fababc02e
Mosquitto MQTT Brokerをブリッジにする
AWSの公式に手順が書かれていたので参考にした。
参考) ローカルのMosquitto MQTT BrokerをブリッジにAWS IoTを使う
https://aws.amazon.com/jp/blogs/news/how-to-bridge-mosquitto-mqtt-broker-to-aws-iot/
参考にした記事は2017年4月に書かれたものなので、このとおりに進めてもうまくいかなかった部分や変更した部分があった。以下、変更して対応した箇所を書きに残す。
>10. これでRoleの作成は完了となりました、AmazonEC2のコンソールへ移動してください。選択するリージョンはAWS IoTが利用できるリージョンを選択します、本記事中はフランクフルトを利用しています。
<2019年現在は東京リージョンでAWS IoTが使えるので、「東京」を選択した
>12. Amazon Linux AMIを選択します
<「Amazon Linux 2 AMI (HVM), SSD Volume Type (x86)」 を選択した
<※当初「Amazon Linux AMI 2018.03.0 (HVM)」で構築してみたのだけれど、「ブラウザから直接マネージド SSH クライアント (アルファ)」でインスタンスに接続ができなかったので作り直した。

>20. 新しいSecurity Groupの作成をします。
>Security group nameに ‘MosquttoBroker’
>Add Ruleで
>Custom TCP Rule / TCP /8883 / 0.0.0.0/0を追加
<MQTTでは標準で1883/TCPポート、SSL(TLS)による暗号化を行う場合には8883/TCPポートを使用。
<今回は暗号化しない通信をするのでポートは「8883」ではなくて「1883」を追加した。
<※インターネットで認証なしの1883ポート開放をするのは望ましくないので、あとでLAN内に移築するつもり。
>22. EC2が起動したら、EC2に接続してください
<インスタンスが起動したあと、「ブラウザから直接マネージド SSH クライアント (アルファ)」で「接続」した。

>23. ログイン後に以下のコマンドを実行してください
<手順どおりではエラーになったので、以下のコマンドでmosquittoをインストールする
<$ sudo amazon-linux-extras install epel
<$ sudo yum install mosquitto
>AWS IoTへのブリッジ設定方法
<この部分は「AWS IoTの設定をする」の手順で終えているので、一部Skipしつつ手で設定した
<※実際はさらっと書かれていて具体的にどうすればいいのかわからなかったため手で設定した。
$ cd /etc/mosquitto
$ sudo mv mosquitto.conf mosquitto.conf.org
$ sudo mkdir certs
$ cd certs
ここ(/etc/mosquitto/certs)に 「AWS IoTの設定をする」の手順でダウンロードしておいた「XXXXXXXXXX-certificate.pem.crt」と「XXXXXXXXXX-private.pem.key」をそれぞれ「cert.crt」「private.key」のファイル名で配置しておく。
インスタンスにアップロードするうまい方法がわからなかったので、viなどエディタを開いてそれぞれ文字列をコピペしてファイルを作成した。ファイルを配置したら、パーミッションをreadに設定しておく。
$ sudo chmod 644 /etc/mosquitto/certs/cert.crt
$ sudo chmod 644 /etc/mosquitto/certs/private.key
ここ(/etc/mosquitto/certs)に「AmazonRootCA1.pem」のroot CA 証明書もダウンロードしておく。
$ sudo wget https://www.amazontrust.com/repository/AmazonRootCA1.pem
mosquittoのブリッジ用設定ファイルを作成する。
nanoやviなどのエディタで、AWSサイトにある雛形をベースにファイルを作成する。
$ sudo vi /etc/mosquitto/bridge.conf
ブリッジ用設定ファイルの雛形のうち、AWS IoTのエンドポイントの部分を自分の環境にあわせて編集する。
東京リージョンを使用した自分の環境では、「xxxxxxxxxxxxxx-xxx.iot.ap-northeast-1.amazonaws.com」のようなアドレスだった。
# ============================
# Bridges to AWS IOT
# ============================
# AWS IoT endpoint, use AWS CLI 'aws iot describe-endpoint'
connection awsiot
address XXXXXXXXXX.iot.region.amazonaws.com:8883
# Specifying which topics are bridged
topic awsiot_to_localgateway in 1
topic localgateway_to_awsiot out 1
topic both_directions both 1
# Setting protocol version explicitly
bridge_protocol_version mqttv311
bridge_insecure false
# Bridge connection name and MQTT client Id,
# enabling the connection automatically when the broker starts.
cleansession true
clientid bridgeawsiot
start_type automatic
notifications false
log_type all
# ============================
# Certificate based SSL/TLS support
# ----------------------------
#Path to the rootCA
bridge_cafile /etc/mosquitto/certs/rootCA.pem
# Path to the PEM encoded client certificate
bridge_certfile /etc/mosquitto/certs/cert.crt
# Path to the PEM encoded client private key
bridge_keyfile /etc/mosquitto/certs/private.key
これでMosquttoを新しい設定で起動する準備ができた。以下のコマンドでMoquttoをバックグラウンドで起動することができる。オプションに「-d」ではなくて「-v」を付けるとmosquittoの動作状況がよく見える。
$ sudo mosquitto -c /etc/mosquitto/bridge.conf -d
ESP32でBLEスキャンしてMQTTで送信する
冒頭にも書いたとおり、WiFiとBluetoothを同時に使うと容量が大きくなるので、Arduino IDEの「ツール」メニューで、「Partition Scheme -> No OTA」(もしくはLarge APPやHuge APP)を選択してコンパイルする。もしArduino IDEの「ツール」メニューにそのようなメニューや選択肢が表示されない場合は、面倒だが手で直接パーティションサイズを編集することもできるらしい。
また、検出したBLEのデータを1つづつMQTTで送信すればそれほど問題にならないだろうが、まとめて送信しようとするとパケットサイズは128を超えてしまうだろう。PubSubClient.h 内のMQTT_MAX_PACKET_SIZEはデフォルトで128が指定されているが、これを1024くらいに書き換えておく。
以下、今回予備実験で使用したソースコードを記載する。githubにUpすればいいものを、まだ実験段階なのでベタで書いておく。
// ESP32でBLEを検出
// MQTTで複数BLEのデータをまとめて送ってみる
#include <WiFi.h>
#include <PubSubClient.h>
#include <BLEDevice.h>
/* BLEスキャン制御用 */
#define DEVICE_NAME "ESP32-01" // ESP32のデバイス名
#define MAX_BLEDEVICES 20 // 最大BLEデバイス数
const int scanning_time = 5; // スキャン時間(秒)
BLEScan* pBLEScan;
// WiFi
const char ssid[] = "xxxxxxxxxxx";
const char passwd[] = "xxxxxxxxxxx";
// Pub/Sub
const char* mqttHost = "xx.xx.xx.xx"; // MQTTのIPかホスト名
const int mqttPort = 1883; // MQTTのポート
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
const char* pubTopic = "localgateway_to_awsiot"; // 送信先のトピック名
//あらかじめ PubSubClient.h 内のMQTT_MAX_PACKET_SIZEを1024に設定しておく
char pubMessage[1024]; // 送信するデータ最大サイズ
void setup() {
Serial.begin(115200);
BLEDevice::init(""); // BLEデバイスを作成する
pBLEScan = BLEDevice::getScan(); // Scanオブジェクトを取得して
pBLEScan->setActiveScan(false); // パッシブスキャンに設定する
// Connect WiFi
connectWiFi();
// Connect MQTT
connectMqtt();
}
void loop() {
sprintf(pubMessage, "{\"device\":\"%s\",\"data\":[", DEVICE_NAME);
BLEScanResults foundDevices = pBLEScan->start(scanning_time);
int count = foundDevices.getCount();
if (count > MAX_BLEDEVICES) {
count = MAX_BLEDEVICES;
}
for (int i = 0; i < count; i++) { // 受信したアドバタイズデータの数ループ
int ble_rssi = 0;
BLEAdvertisedDevice d = foundDevices.getDevice(i);
if (d.haveRSSI()) ble_rssi = d.getRSSI();
BLEAddress ble_addr = d.getAddress();
if (i == 0) {
sprintf(pubMessage, "%s{\"mac\":\"%s\",\"rssi\":\"%d\"}",
pubMessage,ble_addr.toString().c_str(),ble_rssi);
} else {
sprintf(pubMessage, "%s,{\"mac\":\"%s\",\"rssi\":\"%d\"}",
pubMessage,ble_addr.toString().c_str(),ble_rssi);
}
}
// MQTT送信処理
if (count > 0) {
sprintf(pubMessage, "%s]}",pubMessage);
Serial.println(pubTopic);
Serial.println(pubMessage);
mqttClient.publish(pubTopic, pubMessage);
}
// WiFi
if ( WiFi.status() == WL_DISCONNECTED ) {
connectWiFi();
}
// MQTT
if ( ! mqttClient.connected() ) {
connectMqtt();
}
mqttClient.loop();
delay(5000);
}
/**
* Connect WiFi
*/
void connectWiFi()
{
WiFi.begin(ssid, passwd);
Serial.print("WiFi connecting...");
while(WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(100);
}
Serial.print(" connected. ");
Serial.println(WiFi.localIP());
}
/**
* Connect MQTT
*/
void connectMqtt()
{
mqttClient.setServer(mqttHost, mqttPort);
while( ! mqttClient.connected() ) {
Serial.println("Attempting MQTT connection...");
// Create a random client ID
String clientId = DEVICE_NAME;
clientId += String(random(0xffff), HEX);
if ( mqttClient.connect(clientId.c_str()) ) {
Serial.println("connected");
}
delay(1000);
randomSeed(micros());
}
}
あとがき
複数のESP32を一旦Mosquittoで束ねるため、AWS IoTから見ると全デバイスが同一のクライアントIDになる。これの解決方法としては、MQTTのペイロードにデバイスIDを入れておく方法でいいと思う。
あと、ESP32とMosquittoの間は暗号化や認証をしていないので、セキュリティ的に課題がありそう。やはりRaspberry Piなどで構成したほうが融通が利くしセキュリティ的にも安心か。
とりあえずAWSのEC2上に作ったMosquittoはESP32の同一LAN上に置くサーバーに移すことにする。
参考) Mosquittoを使ったデバイス<->AWS IoTのブリッジ接続に関する考察
https://dev.classmethod.jp/cloud/aws/mosquitto-bridge-to-awsiot/
この先続けるとしたら、「Raspberry Pi Zero W」を使ってみようと思う。
今日はここまで。
