kghr IT備忘録
Android, Arduino, 格安sim, ipカメラ, Unity開発 など、試したことやメモを書きます。
最新記事
3Dプリンター(X-maker)のベンチマークをしてみた
2021.02/09 (Tue)

新しいフィラメントが届いたので、使い勝手を確認するためにベンチマークに使われるタグボートとキューブを出力してみました。ここまで出力できればいまのところ十分なレベルだった備忘録。
購入したフィラメント
3D プリンターに付属していたのは赤い500gのPLAだったのですが、もう残り少なくなってきました。Amazonで物色していたらお手頃な価格でなんとなく良さそうだったので、DEMA 3D社の黒いPLAを購入しました。
実際に届いた品には、「PLA-F」と書いてありました。PLAとの違いはあまりわかりませんでしたが使えるならいいか。
真空パックで乾燥剤も入っていたし、巻きはきれいだったので特に問題なさそう。試しに小物を出力してみたのですが、以前使っていたPLAに比べて柔らかめです。210℃で微妙に糸を引く感じだったので、195℃くらいにしました。
ベンチマーク
Benchy
有名なタグボートの3Dモデルですよね。今回初めて出力してみました。
第一印象はもう十分。細かいこと言えばキリがないのでしょうが、だいたい出力されればそれでいいです。

後ろから


上と下から
屋根もきれいだし、船底の文字も読めます。船底はテーブルについていたので白色になっていたのでライターであぶってあります。


横から
フィラメントの切れ目はちょっとボコボコしています。スライサーと比較しても当たり前ですが同じ場所でした。まあこのくらい大したことはありません。

XYZキューブ
こちらも良く見るベンチマークのモデルでしょう。XYZの文字がエンボスになっているキューブです。

XYZ面
上面のZはちょっとザラザラしていますが、まあこんな感じでしょう。



側面と底面
これくらいきれいに出力されるならもう十分かな。


角の継ぎ目
フィラメントの継ぎ目はどうしても出ますよね。もっと細かく出力すれば目立たなくなるのだと思いますが、こだわりません。

あとがき
とりあえず、3Dプリンターもフィラメントも使えそうな感じでよかった。フィラメントは200円クーポンが付いてたから、今のうちに他の色も買っておこうかと思っています。
今日はここまで。
スポンサーサイト
遊星歯車の時計を作ってみた
2021.02/08 (Mon)

昨年末に3Dプリンターを購入したばかりですが、ネットを見ていたら面白そうな時計があったので作ってみました。オリジナルのサイズでは自分の3Dプリンターには大きすぎたので、サイズを小さくしつつ構成を変えて対応した備忘録です。
今回作成したもの

instructablesにある「Planetary Gear Clock」がそれ。
機械式時計でありつつ、ステッピングモーターとArduinoで動かすという組み合わせ。
長針が太陽歯車についていて、これが12回転すると遊星歯車についている短針が1回転するようになっています。
サイズの変更
わたしが年末に購入した3D-Printer(X-Maker)は出力サイズが15cmx17cmまでとなっています。オリジナルは文字盤の丸い部分だけでも直径30cmくらいあって、出力しきれません。

最初はスライサーで縮尺を50%に設定して出力したのですが、どうも単純に小さくしただけでは歯車のかみ合わせがうまくないようです。結局すべてのパーツをCADソフトで調整しなおしました。
まず文字盤のサイズをオリジナルの半分の直径15cm弱に変更しました。文字盤を簡略化して足をつけ、ギリギリ出力サイズの15cmx17cmに収めました。

ネジの変更
ネジ穴はもともとM5用だったので、単純にサイズを半分にするとM2.5になります。手持ちのネジにM3があったので、ネジ穴はM3用に変更しました。もともと6か所あったものを4か所に変更するなどの変更も加えています。
歯車の変更
歯車はそのまま出力すると、接地面に微妙なバリが出てかみ合わせが良くありません。

ラフトを付ければバリは出なくなるのですが、表面が美しくなく、出力にも時間がかかります。

そこで、バリが出ても歯の中に納まるように接地面の歯の角を少しだけ削りました。


プーリ歯車の変更
全体のサイズを小さくしたいのですが、プーリ歯車はタイミングベルト幅の関係もあり元のサイズで使います。しかし、Amazonでオーダーして実際に手にとってみると結構重くて無理がありそうでした。
そこで、プーリ歯車も3D プリンターで出力することにしました。ステッピングモーターを"28BYJ-48"に変更して、軸にすっぽりハマる形状にしました。


ステッピングモーターの変更
オリジナルで使っているステッピングモーターは"42BYGHW-208"という一辺が4.2cmくらいの大きさで200gくらいの重さがありますが、これを"28BYJ-48"に変更しました。トルクは小さくなりますが、本体が軽量化されているので大丈夫でしょう。
ステッピングモーターのドライバもついて300円弱ですし、経済的にも良さそう。"28BYJ-48"は5Vタイプと12Vタイプがあるようですが、電源の取り回しがしやすいので5Vのタイプを選択します。
ステッピングモーターを取り付けるために台座を作りました。タイミングベルトの張り具合が調整できるようにスライドで固定できるようにしています。

実際の取付具合はこんな感じです。

電気回路の変更
本体のサイズを半分にしてステッピングモーターを取り付けたら、電気回路を取り付けるスペースがなくなってしまいました。無理に本体に付ける必要もないので、電気回路は分離して箱にでも収めることにします。ACアダプタのような感じですね。
適当な基板があったので、ステッピングモーターのドライバとArduino nano、RTCをレイアウトしてハンダ付けしました。ホールセンサーは省略して、ボタンは2つで時刻合わせをします。

スケッチの変更
ステッピングモーターの駆動対応、ボタン2つの時刻合わせ、ステップ数誤差の吸収などについて、微調整しています。
RTCモジュール対応
RTCモジュールをDS1307のものから小型のDS3231に変更しました。
//RTC_DS1307 rtc;
RTC_DS3231 rtc;
ステッピングモーター対応
ここではGPIO3,4,5,6を使っています。オリジナルとはパラメータの順番が異なるので注意。
//AccelStepper Stepper1(8,5,4,2,3,false);
AccelStepper Stepper1(8,3,5,4,6);
ボタン2つの時刻合わせ対応
オリジナルで、①シングルクリック(30秒送り)、②ダブルクリック(1時間送り)が定義してあるのですが、それに加えて③長押し(5分送り)を追加しました。
↓void setup()
TIME1.attachDuringLongPress(longPress2);
TIME2.attachDuringLongPress(longPress4);
void longPress2()
{ rtc.adjust(DateTime(rtc_time - TimeSpan(0,0,5,0)));
}
void longPress4()
{ rtc.adjust(DateTime(rtc_time + TimeSpan(0,0,5,0)));
}
ホールセンサーのキャリブレーション無効対応
回路の簡略化のためにホールセンサーを省略したので、呼び出さないようにコメントアウトします。
↓void setup()
//calibrate();
ステッピングモーターのステップ数対応
ステッピングモーターを変えたことで、1分あたりのステップ数が変わりました。元々は1分(3,600秒)あたり1,200ステップ刻んでいるようなのでパラメータは"3"なのですが、"28BYJ-48"では1分(3,600秒)あたり12,288ステップ刻むのでパラメータは"0.29296875"です。1秒あたりに整数で刻んでいくと誤差が生じるため、75秒あたり256ステップ刻むタイミングで吸収するようにしました。
↓// Variable declaration:
long xsteps;
↓// Definitions
//#define Sec_per_step 3L
#define Sec_per_step 0.292
↓void calc_steps()
// steps = steps + (seconds/Sec_per_step) ;
steps = steps + (seconds/Sec_per_step)-xsteps ;
↓void move_stepper()
void move_stepper()
{
//Stepper1.enableOutputs();
Stepper1.move(dir*steps);
Stepper1.runToPosition(); .
// clock_time = rtc_time;
xsteps = xsteps + steps;
if (xsteps >=256) {
clock_time = rtc_time ;
xsteps = -xsteps % 256;
}
steps = 0;
//Stepper1.disableOutputs();
}
あとがき
3Dプリンターはいろんなパーツをその場で生み出すことが出来るのでとても素晴らしい。今回はサイズを変更したことで試行錯誤もあり、動かすまでにはそれなりの数の試作品を出力したのだけれど、それも楽しかった。

この時計のほかにも年末からいろいろ出力していたら、最初に付属していたフィラメントも残り少なくなりました。次はこの黒色を使ってみようと思います。
今日はここまで。
3Dプリンター(X-maker)を買いました!初日に分解しました!
2020.12/26 (Sat)
ついに3Dプリンターを買いました。以前から使ってみたいとは思っていましたが、そろそろ成熟してきた感じがあったので年末年始のお楽しみとしてQIDI TECHNOLOGY社の X-makerを購入。他の3Dプリンターとの比較はできませんが、シンプルで使いやすいです。
すばらしく順調に稼働したあと、フィラメントをしまおうとしてトラブル発生。フィラメントを押しても引いても抜けなくなりヘッダを分解して解決した次第。この製品には分解の動画もついているのでサポートに頼ることなく解決した経緯などを書きます。
機器の選定
これまで何度か買いそうになったのが、ANYCUBIC社の MEGA-Sでした。MEGA-SはAmazonの評価数が多く値段もソコソコで評判もよいのですが、低評価が10%くらいあるのが気になって結局思いとどまりました。
そして、今回購入したのはQIDI TECHNOLOGY社の X-makerです。Amazonで注文してすぐ届きました。
選定の理由は以下のとおり。
・筐体が箱型
電子レンジのような見た目で部屋に設置するに収まりがいい。工作室ならいいけれど、シャフトは油が塗ってあるからむき出しだとホコリが付くだろうし、フィラメントやワイヤがむき出しで動くのは不安だもの。
他の箱型筐体のモデルとも比べてみたのですが、なんとなくデザイン的にもコレかなと。
・評判がよさそう
Amazonの評価を見るとなかなか良さげ。トラブル時のサポートも早くてやりとりが日本語OK、説明もビデオで送ってくれるとか。
低評価の書き込みは「初心者向けと書いてあるのに初心者が使えない」という、わけのわからないものだけでした。初心者用のバイオリンだって練習しなきゃ弾けないよね、と思うのですが。
・値段もソコソコ
税込みで5万円を切る価格も頃合いかな。
同じメーカーで、ちょっとお安いX-Smartというモデルがありますが、こちらはX-makerのほうが新しいモデルだとか。また、もうちょっとお安いX-one2というモデルは出力サイズがひと回り小さいようです。
・使いやすそう
据え付けてすぐ使える感じ。実際テーブルのレベル調整も液晶で説明が表示されるし分かりやすい。
開梱して据え付けるまで
そもそも注文してから到着するまでが早かった。注文日から中一日あけて翌朝9:30には届いているというストレスのなさ。Amazon恐るべし。
さて、先人のブログで見たとおり、箱はほどほどに大きくて、「Overweight」のシールも貼ってありました。

サイズはだいたい50cmの立方体な感じです。

さて、箱を開けてみるとこんな感じでパーツ類が入っていました。(ここに取り出し方をガイドした冊子があるので、見ながら開梱することをお勧めします)

パーツ類の入っている発泡スチロールを取り除くと、いよいよ本体のお目見え。

本体を取り出しました。側面に取っ手があるので、すっと上に抜くことができます。しっかり梱包されています。

本体を取りだしたあとはカラの発泡スチロールのみ。念のため撮っておきました。

梱包のテープの端はめくりやすいように折りたたんでありました。結構細かい気配り。

がっちり留めてあるところも、本体に傷つかないようにプチプチを挟んであったり。好印象。

底面も撮っておきました。隙間から電源装置や基板が見えます。

そうそう、側面はフタがなくふさがれていました。また少しデザインが変わったのかな。

電源は115Vにセットしてありました。日本で使うのはこれでOK。

発泡スチロールの1段目に入っていたパーツ類を広げてみました。

冊子類はこんな感じ。

同梱されているパーツ類が載っています。あれ、そういえばさっきUSBメモリーがなかったな。

USBメモリーは本体の中の発泡スチロールに収まっているみたい。ガイドでは「U disk」と書いてあります。

USBメモリーとスティックのりが出てきました。

これで全部そろっています。

そうだ。電源コードが3本のタイプだった。

こういったときは延長コードを使ったりしてアース棒をはみ出させます。本体の電源スイッチが背面に位置しているため、100円ショップの電源スイッチと組み合わせて使い勝手を良くしました。

フィラメントのガスケットがハマりにくかったので、内側に少しヤスリをかけました。

テーブルの調整とフィラメントの取付を終えて、いよいよ出力してみます。テスト用の謎パーツです。

無事出力されました!うーん、なんとすんなり出力されるのだろうか。

謎パーツの内側を覗くと、何本か糸を引いていました。フィラメントにあわせて温度や速度を調整するときれいに出るそうです。

底はこんな感じ。簡単に剥がせました。

簡単そうなモデルを出力
まずスライサーのソフト Qidi Print をインストール。
このソフトは同梱のUSBメモリーの中に入っていましたが、ここにあるもののほうがバージョンが新しかったです。
ネットからダウンロードしたモデルを出力
フリーの素材をダウンロードしてきました。ではバラに挑戦してみましょう。
Googleの画像検索で「stl roses」を検索して出てきたものから選んでみました。
こちら -> http://3dmag.org/en/market/item/1719/

おお、こんな感じでできちゃうのか。すごいなぁ。
ちょっと糸を引いたりデコボコした部分がありますが、いい感じになるように温度や速度を調整するのはあとで。

フィラメントが詰まった
ここでフィラメントを取り出そうとして四苦八苦。押しても引いてもフィラメントが抜けなくなってしまいました。
分解ビデオがなかったら初日にして撃沈していたと思いますが、意外にすんなり解決しました。ヘッダを取り外して分解するのに小一時間かかりましたが、なんとかなりそうな難易度でした。
分解方法を調べる
USBメモリーの中に分解手順のビデオがあるって書いてあったので、早速チェック。
ありました -> \X-Maker\5.The solution videos of common issues のフォルダです。

「Replacement extruder.mp4」を参考にヘッダを取り外しました。

「the video of cleaning the extruder feeder_1.mp4」を参考にヘッダを分解しました。

フィラメントは送り出しも出来ず引き抜きもできない状態で引っかかってました。曲がったところをカッターで切ってフィラメントを除去します。

詰まった原因は、フィラメントを抜くときにボタン操作を間違ってしまったからだと思います。なかなか出てこないなぁと出してみたり引いてみたりしたのがいけなかったのでしょう。
フィラメントを抜く際には、一旦出してから引っ込めるのが必要だと説明書に書かれていました。マニュアルを読んでから操作しましょう。

可動式のタコ
このタコを出力してみたかったんですよね。
-> https://www.thingiverse.com/thing:3495390

おお、順調に出力されているっぽい。

出来ました!

台から取り外している段階から脚がシャランと。

ふう。なかなか使えそうだ。

あとがき
良くできてるなぁ。
200℃近い温度でフィラメントを溶かすのだから匂いはそれなりにするけれど、それほどイヤな感じではないです。
音に関しては思ってたより静かで、動いている最中に電話しても大丈夫なくらい。インクジェットプリンターの印刷音よりマシだと思う。フィラメントの種類によっては上部のアクリルカバーを外す必要があるらしいのですが、まあ大丈夫でしょう。
とりあえず同梱のフィラメントだけでも年末年始を過ごせそうです。
今日はここまで。
ESP32のWiFi設定をWeb サーバで行ってみた
2020.08/22 (Sat)

ESP32はWiFi機能を持っているが、そのSSIDとパスワードをスケッチに直書きしてしまうと、設定を変更するたびにコンパイルしなおさなければならない。
やはりブラウザ経由だよねと、ということで有名どころの"WiFi Manager for ESP32"のライブラリを使ってみたものの、標準でない前提ライブラリがいくつかあったりでうまく動作せず。
そして、いくつか派生バージョンも試してみたものの、安定性に難があったりで使うのを断念。
結局、シンプルなものを作ったほうが早いのではないかということで、WiFi設定だけでなく比較的自由にパラメータを設定できるような仕組みを作ってみた備忘録。
参考にした記事:
https://qiita.com/nanbuwks/items/171d830c92617702ad7e
今回作成したものの仕組み
ESP32を起動したあと、設定ファイルを読み込んでWiFiに接続できなかったら以下の流れで設定する。
① ESP32をアクセスポイント化する
ESP32のアクセスポイントが立ち上がっているときは、BUILTINのLEDが点灯する。手元の開発キットだとBUILTINのLEDはGPIO2に設定されている。
WIFI_AP モードでWebサーバーを立ち上げたあとの”delay(200)”が重要。これがないとESP32がPanicを起こしてしまうようだ。
このアクセスポイントは一時的に立ち上がるものなので、パスワードなしにしておいた。
WiFi.mode(WIFI_AP);
WiFi.softAP(WIFIMGR_ssid); // no password
delay(200); // Important! This delay is necessary
WiFi.softAPConfig(apIP,apIP,IPAddress(255,255,255,0));
② 自動的にWiFiの設定画面に切り替わる
スマートフォンなどでESP32で立ち上げたアクセスポイント ”WIFIMGR_ESP32” に接続すると、自動的に設定のトップ画面が表示されるようにした。
DNSサーバーの機能を使って、wifimgr_top()の処理を実行。 wifimgr_top() では、WiFi設定画面のトップページを表示する。
dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
dnsServer.start(53, "*", apIP);
webServer.on("/", wifimgr_top);
:
webServer.onNotFound(wifimgr_top);
webServer.begin();

③ WiFi接続のSSIDとパスワードを設定する
ESP32で周辺のWiFiアクセスポイントをスキャンし、プルダウンリストから選択できるようにする。パスワードを設定して[SET]ボタンを押せば設定ファイルに記録される。

設定ファイルは ”SPIFFS.h” を使って読み書きする。設定ファイルの中身は単純に、1行目がSSIDで2行目がパスワードとした。パスワードは平文だが、暗号化するならコードをちょっと足せばいい。
設定ファイル /config.itxt の中身:
myssid
12345678
④ ESP32をリブートする
トップ画面の[Reboot]ボタンを押せば、リブートの確認画面が表示されたあとESP32をリブートする。WiFiアクセスポイントとパスワードが設定されたので、起動時にはすんなり接続されるはず。


作成したスケッチ
以下に今回作成したスケッチを記載する。
なお、設定ファイルの保管にSPIFFSを使用するため、コンパイル時のオプションで Pertirion Scheme: "Default with spiffs" を選択しておくこと。
/*
WIFI_MGR for ESP32
by kghr labo 2020
*/
// include the library code:
#include <WiFi.h>
#include <WebServer.h>
#include <DNSServer.h>
#include "SPIFFS.h"
//#define LED_BUILTIN 2;
IPAddress apIP(192,168,1,100);
WebServer webServer(80);
const char* WIFIMGR_ssid = "WIFIMGR_ESP32";
const char* WIFIMGR_pass = "xxxxxxxx";
DNSServer dnsServer;
// parameters setting
const String defaultSSID = "myssid";
const String defaultPASSWD = "12345678";
String ssid = defaultSSID;
String passwd = defaultPASSWD;
// scan SSID
#define SSIDLIMIT 30
String ssid_rssi_str[SSIDLIMIT];
String ssid_str[SSIDLIMIT];
// SPIFFS config filename
const char* configfile = "/contig.txt";
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
// init filesystem
if(!SPIFFS.begin(true)){
Serial.println("SPIFFS Mount Failed");
}
// read config from SPIFFS
readConfigFile();
// wifi connect
uint8_t retry = 0;
WiFi.begin(ssid.c_str(), passwd.c_str());
while (WiFi.status() != WL_CONNECTED) {
Serial.print('.');
delay(200); // 200ms
retry ++;
if (retry > 50) { // 200ms x 50 = 10 sec
Serial.println("wifi connection timeout");
webconfig(); // enter webconfig
}
}
Serial.println();
Serial.printf("Connected, IP address: ");
Serial.println(WiFi.localIP());
delay(500);
}
void loop() {
// put your main code here, to run repeatedly:
delay(100);
}
//*******************************************
void webconfig() {
Serial.println("WebConfig mode: ");
digitalWrite(LED_BUILTIN, HIGH);
configserver();
uint8_t configloop = 1;
while (configloop == 1) {
dnsServer.processNextRequest();
webServer.handleClient();
}
digitalWrite(LED_BUILTIN, LOW);
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
}
void configserver() {
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
delay(100);
WiFi.mode(WIFI_AP);
WiFi.softAP(WIFIMGR_ssid); // no password
// WiFi.softAP(WIFIMGR_ssid,WIFIMGR_pass); // with 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("/", wifimgr_top);
webServer.on("/wifiinput", HTTP_GET, wifiinput);
webServer.on("/wifiset", HTTP_GET, wifiset);
webServer.on("/reboot", reboot);
webServer.on("/doreboot", doreboot);
webServer.onNotFound(wifimgr_top);
webServer.begin();
}
String maskpasswd(String passwd){
String maskpasswd = "";
for (int i=0; i<passwd.length(); i++) maskpasswd = maskpasswd + "*";
if (passwd.length() == 0) maskpasswd = "(null)";
return maskpasswd;
}
void wifimgr_top() {
String html = Headder_str();
html += "<a href='/wifiinput'>WIFI setup</a>";
html += "<hr><h3>Current Settings</h3>";
html += "SSID: " + ssid + "<br>";
html += "passwd: " + maskpasswd(passwd) + "<br>";
html += "<hr><p><center><a href='/reboot'>Reboot</a></center>";
html += "</body></html>";
webServer.send(200, "text/html", html);
}
String Headder_str() {
String html = "";
html += "<!DOCTYPE html><html><head>";
html += "<meta name='viewport' content='width=device-width, initial-scale=1.3'>";
html += "<meta http-equiv='Pragma' content='no-cache'>";
html += "<meta http-equiv='Cache-Control' content='no-cache'></head>";
html += "<meta http-equiv='Expires' content='0'>";
html += "<style>";
html += "a:link, a:visited { background-color: #009900; color: white; padding: 5px 15px;";
html += "text-align: center; text-decoration: none; display: inline-block;}";
html += "a:hover, a:active { background-color: green;}";
html += "bo32{ background-color: #EEEEEE;}";
html += "input[type=button], input[type=submit], input[type=reset] {";
html += "background-color: #000099; border: none; color: white; padding: 5px 20px;";
html += "text-decoration: none; margin: 4px 2px;";
html += "</style>";
html += "<body>";
html += "<h2>WIFIMGR</h2>";
return html;
}
void InitialConfigFile(){
Serial.printf("SPIFFS initial file: %s\n", configfile);
ssid = defaultSSID;
passwd = defaultPASSWD;
WriteConfigFile();
}
//*******************************************
void WriteConfigFile(){
ssid.trim();
passwd.trim();
Serial.printf("SPIFFS writing file: %s\n", configfile);
File fw = SPIFFS.open(configfile, "w");
fw.println(ssid);
fw.println(passwd);
fw.close();
delay(100);
}
//*******************************************
void readConfigFile(){
String numstr;
Serial.printf("SPIFFS reading file: %s\n", configfile);
File fr = SPIFFS.open(configfile, "r");
if (fr) {
ssid = fr.readStringUntil('\n');
ssid.trim();
if (ssid =="") ssid = defaultSSID;
passwd = fr.readStringUntil('\n');
passwd.trim();
if (passwd == "" && ssid == defaultSSID) passwd = defaultPASSWD;
fr.close();
} else {
//InitialConfigFile
Serial.println("read open error");
Serial.print("SPIFFS data seems clash. Default load...");
InitialConfigFile();
}
}
void wifiinput() {
String html = Headder_str();
html += "<a href='/'>TOP</a> ";
html += "<hr><p>";
html += "<h3>WiFi Selector</h3>";
html += WIFI_Form_str();
html += "<br><hr><p><center><a href='/'>Cancel</a></center>";
html += "</body></html>";
webServer.send(200, "text/html", html);
}
//*******************************************
String WIFI_Form_str(){
Serial.println("wifi scan start");
// WiFi.scanNetworks will return the number of networks found
uint8_t ssid_num = WiFi.scanNetworks();
Serial.println("scan done\r\n");
if (ssid_num == 0) {
Serial.println("no networks found");
} else {
Serial.printf("%d networks found\r\n\r\n", ssid_num);
if (ssid_num > SSIDLIMIT) ssid_num = SSIDLIMIT;
for (int i = 0; i < ssid_num; ++i) {
ssid_str[i] = WiFi.SSID(i);
String wifi_auth_open = ((WiFi.encryptionType(i) == WIFI_AUTH_OPEN)?" ":"*");
ssid_rssi_str[i] = ssid_str[i] + " (" + WiFi.RSSI(i) + "dBm)" + wifi_auth_open;
ssid_rssi_str[i] = ssid_str[i] + wifi_auth_open;
Serial.printf("%d: %s\r\n", i, ssid_rssi_str[i].c_str());
delay(10);
}
}
String str = "";
str += "<form action='/wifiset' method='get'>";
str += "<select name='ssid' id ='ssid'>";
for(int i=0; i<ssid_num; i++){
str += "<option value=" + ssid_str[i] + ">" + ssid_rssi_str[i] + "</option>";
}
str += "<option value=" + ssid + ">" + ssid + "(current)</option>";
if (ssid != defaultSSID){
str += "<option value=" + defaultSSID + ">" + defaultSSID + "(default)</option>";
}
str += "</select><br>\r\n";
str += "Password<br><input type='password' name='passwd' value='" + passwd + "'>";
str += "<br><input type='submit' value='set'>";
str += "</form><br>";
str += "<script>document.getElementById('ssid').value = '"+ ssid +"';</script>";
return str;
}
void wifiset(){
ssid = webServer.arg("ssid");
passwd = webServer.arg("passwd");
ssid.trim();
passwd.trim();
WriteConfigFile();
// 「/」に転送
webServer.sendHeader("Location", String("/"), true);
webServer.send(302, "text/plain", "");
}
void reboot() {
String html = Headder_str();
html += "<hr><p>";
html += "<h3>reboot confirmation</h3><p>";
html += "Are you sure to reboot?<p>";
html += "<center><a href='/doreboot'>YES</a> <a href='/'>no</a></center>";
html += "<p><hr>";
html += "</body></html>";
webServer.send(200, "text/html", html);
}
void doreboot() {
String html = Headder_str();
html += "<hr><p>";
html += "<h3>rebooting</h3><p>";
html += "The setting WiFi connection will be disconnected...<p>";
html += "<hr>";
html += "</body></html>";
webServer.send(200, "text/html", html);
// reboot esp32
Serial.println("reboot esp32 now.");
digitalWrite(LED_BUILTIN, LOW);
delay(2000); // hold 2 sec
ESP.restart(); // restart ESP32
}
あとがき
設定パラメータの追加とかスケッチにページを足していけば自由に拡張していくことができる。ラジオボタンやチェックボックスなどレイアウトもしやすいので、これをベースに広げていけそうな予感。
GPIOにつないだスイッチを押したら設定画面に移行するなどの動きも、スケッチを足していけば自由にできるしいいんじゃないだろうか。
なにせESP32のアクセスポイントに接続したら自動的に設定画面が表示されるところが気に入っている。参考になれば幸い。
画面幅に合わせるなどcssをもうちょっとちゃんと書けばよさそうだけど、まあいいか。
今日はここまで。
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"でも動作した。
とりあえずここでは動くようになったから良しとする。
今日はここまで。
