こんにちは、ENGかぴです。
ESP32の特徴であるWiFi通信をWiFiライブラリを使用しアクセスポイントを実装します。スマホなどのブラウザからESP32に接続して温湿度センサー(SHT35-DIS)から取得したデータを表示する方法をまとめています。
ESP32-WROOM-32E開発ボード(秋月電子)を使用しArduino IDEで開発を行います。また、AE-SHT35(秋月電子)を使用しています。
ESP32-WROOM-32Eを使って動作確認したことをまとめています。
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
WiFiのアクセスポイントを実装
ESP32-WROOM-32EにおいてWiFiライブラリを使用することでアクセスポイントやサーバーとして動作させることでクライアント(スマホ等)からの接続に対して応答することができます。
SHT35-DISからデータ取得する方法については下記記事と同様の処理としています。
ESP32-WROOM-32EのWireでSTH35の情報を取得
WiFiのアクセスポイントを追加実装して取得したデータをWiFi通信して表示します。
定数の準備
const char *ssid = "EngKapi1"; //SSID
const char *pass = "22223333"; //password
const IPAddress ip(192,168,11,2); //IPアドレス
const IPAddress subnet(255,255,255,0); //サブネットマスク
アクセスポイントとして動作させるためにSSIDとPASSを設定する必要があります。初期化時に設定する必要があるため定数のポインタとして宣言しています。
SSIDはWiFiのリストとして表示される名称で任意の名称にすることができます。パスワードはWiFiのアクセスポイントに接続するためのパスワードです。
アクセスポイントのIPアドレス及びサブネットマスクをIPAddressの型の変数として宣言します。ゲートウェイの設定はローカルで外部接続しないため行う必要はありません。
アクセスポイントの設定
WiFiClient client; //クライアント情報に関する変数
WiFiServer server(80); //ポート80(http)の接続を管理する変数
void setup() {
WiFi.softAP(ssid, pass); //WiFiのアクセスポイントの設定
WiFi.softAPConfig(ip, ip, subnet); //IP及びサブネットマスクの設定
server.begin(); //サーバーの開始
}
WiFi.softAP()で第1引数と第2引数にアクセスポイント名とパスワードを設定します。第3引数から第5引数までは変更しない場合は特に設定する必要はありません。
第3引数はチャンネル番号を1~13chで指定します。デフォルトでは1chを使用するようになっています。
第4引数はSSIDを全体に通知するかの設定を行います。デフォルトではブロードキャストになっており接続範囲であればSSIDが通知されます。SSIDを隠したい場合は0を指定します。デフォルトでは1で通知になっています。
第5引数は最大で接続できるクライアント数です。1~4を指定します。デフォルトでは4台になっています。
WiFi.softAPConfig()の第1引数にIPアドレスをしてします。第2引数はゲートウェイを指定します。ゲートウェイを使用しない場合はIPアドレスを設定します。第3引数はサブネットマスクを指定します。
server.begin()でサーバーが指定したポートで動作開始します。宣言時に80を指定しているため80ポートで動作します。
クライアント接続の処理
void WifiSeverMain(void){
WiFiClient client = server.available();
if( client ){
String str = "";
Serial.println("New Client.");
while(client.connected()){
if(client.available()){
char c = client.read();
Serial.write(c);
if( c == '\n'){ //改行コード 0x0A
if( str.length() == 0 ){ //クライアントの文字列取得後、以下の処理
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println();
client.print(HtmlSet()); //Htmlを送信
client.println();
client.stop(); //クライアント接続を停止
Serial.println("Client Disconnected.");
}else{
str = "";
}
}
else if( c != '\r'){ //復帰コード 0x0D
str += c;
}
}
}
}
}
クライアント接続を常時監視して接続要求を待ちます。接続要求があればクライアント接続中となり内部の処理を行います。
クライアントは接続要求するとクライアント情報を送信するためclient.read()によって文字を取得しシリアルモニタに表示します。デバッグのために表示するようにしていますが不要であれば必要ありません。
クライアントの文字列取得後はクライアントにclient.print()及びclient.println()を使用してHTMLデータを送信します。
HTMLデータを送信した後はclient.stop()でクライアント接続を停止します。
広告
ブラウザに表示するためのHTMLデータの生成
String HtmlSet(void){
String str = "";
str += "<html lang=\"ja\">";
str += "<head>";
str += "<meta http-equiv=\"refresh\" content=\"5\">";//5秒おきにリフレッシュ
str += "<meta charset=\"UTF-8\">";
str += "<title>Sensor SHT35</title>";
str += "</head>";
str += "<body>";
str += "<h1>ESP32-SHT35-DIS温湿度センサ</h1>";
//省略
str += "</body>";
str += "</html>";
return str;
HTMLデータの作成方法は様々ですがString型を使って文字列を生成して送信する方法を紹介します。strをString型で宣言してHTMLデータになるように文字列を付け加えていき戻り値として返します。
client.print()はHtmlSet()の戻り値の文字列がセットされるためHTMLデータを送信することができます。
HTMLデータの生成がうまくいかないとブラウザでの表示が真っ白になってしまうため注意が必要です。
Google Chromeではブラウザ上でF12を押すとページの情報を確認することができるためHTMLデータのエラーの確認ができます。少し見にくいため参考程度になりますがヒントがないよりかはましです。
動作確認
ESP32-WROOM-32EとSHT35-DISの配線例を示しています。回路図の番号はESP32-WROOM-32Eの左上を1ピンとした時反時計回りにピンを数えた場合の番号としています。ピン番号横の()内の番号はシルク印刷されているピンの名称です。
スマホなどWiFi通信が可能な機器にSSIDである「EngKapi1」が表示されています。パスワードを入力して接続します。
Google ChromeなどのブラウザでIPアドレス「192.168.11.2」を入力するとESP32-WROOM-32Eから接続先にHTMLデータを送信しSHT35-DISから取得した温湿度データを表示します。
通信端末によってはWiFi通信が外部のインターネットに接続されないため接続するかを尋ねられることがありますが接続を許可する必要があります。
HTMLデータに5秒おきに画面をリフレッシュするようにしているため5秒ごとに温湿度情報が更新されていることが確認できました。同時にシリアルモニタの表示を確認しました。
スマホからアクセスするとNew Clientを表示しクライアントから接続元(スマホ)の情報を受け取っていることが分かります。
クライアントから情報を取得した後はHTMLデータを送信してスマホの表示をしていますがHTMLデータの送信が終わるとClient Disconnected.を表示しています。
キャッシュが残っているかによってクライアントから接続要求が2度送信されることがあることも分かりました。どちらにしてもスマホ上でデータが更新されているため問題ないと考えています。
ソースコード全体
以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。
#include <Wire.h>
#include <WiFi.h>
#define SLAVE_ADRS 0x45
#define POLYNOMIAL 0x31
#define PIN_DI 22
#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define BASE_CNT 10 //ベースタイマカウント値
#define TIME_SHT35_MAX 100 //SHT35の計測タイマ
#define TIME_OUT_MAX 20 //SHT35の通信タイムアウト
typedef enum{
SHT35_MEASURE = 0,
SHT35_WAIT,
SHT35_READ,
SHT35_MAX
}SHT35_MODE;
const char *ssid = "EngKapi1"; //SSID
const char *pass = "22223333"; //password
const IPAddress ip(192,168,11,2); //IPアドレス
const IPAddress subnet(255,255,255,0); //サブネットマスク
/* 変数宣言 */
WiFiClient client;
WiFiServer server(80);
SHT35_MODE mode = SHT35_MEASURE;
uint32_t beforetimCnt = millis();
float temp;
float humi;
int16_t timSht35start;
int16_t timSht35Out = TIME_OFF;
uint8_t singleshot[2] = { 0x2C, 0x06};
uint8_t chksum[2];
uint8_t rxdata[6];
/* プロトタイプ宣言 */
void mainApp(void);
void Sht35Measure(uint8_t dev_id,uint8_t* cmd, uint16_t len);
bool Sht35GetData(uint8_t dev_id,uint8_t* reg_data, uint16_t len);
uint8_t Crc8Calc(uint8_t *data, uint8_t sz );
void WifiSeverMain(void);
String HtmlSet(void);
void setup() {
Serial.begin(115200);
Wire.begin();
WiFi.softAP(ssid, pass); //WiFiのアクセスポイントの設定
WiFi.softAPConfig(ip, ip, subnet); //アクセスポイントのIP及びサブネットマスク
server.begin(); //サーバーの開始
}
void loop() {
mainTimer();
mainApp();
WifiSeverMain();
}
/* タイマ管理 */
void mainTimer(void){
if ( millis() - beforetimCnt > BASE_CNT ){
beforetimCnt = millis();
if( timSht35start > TIME_UP ){
--timSht35start;
}
if( timSht35Out > TIME_UP ){
--timSht35Out;
}
}
}
/* メイン処理関数 */
void mainApp(void){
switch(mode){
case SHT35_MEASURE:
if( timSht35start == TIME_UP ){
timSht35start = TIME_OFF;
timSht35Out = TIME_OUT_MAX;
Sht35Measure(SLAVE_ADRS, &singleshot[0], sizeof(singleshot));
mode = SHT35_WAIT;
}
break;
case SHT35_WAIT:
if( digitalRead(PIN_DI) == 1){
mode = SHT35_READ;
}
break;
case SHT35_READ:
if( Sht35GetData(SLAVE_ADRS, &rxdata[0], sizeof(rxdata))){
uint16_t tempHex;
uint16_t humiHex;
chksum[0] = Crc8Calc(&rxdata[0],2); //tempのCRCチェック
chksum[1] = Crc8Calc(&rxdata[3],2); //humiのCRCチェック
if( chksum[0] == rxdata[2] && chksum[1] == rxdata[5]){
tempHex = ((uint16_t)rxdata[0] << 8) | rxdata[1];
humiHex =((uint16_t)rxdata[3] << 8) | rxdata[4];
temp = (tempHex / 65535.00) * 175 - 45;
humi = (humiHex / 65535.0) * 100.0;
Serial.print("temp: ");
Serial.print(temp);
Serial.print("℃ ");
Serial.print("humi :");
Serial.print(humi);
Serial.print("% ");
Serial.println();
}
timSht35start = TIME_SHT35_MAX;
timSht35Out = TIME_OFF;
mode = SHT35_MEASURE;
}
break;
}
if( timSht35Out == TIME_UP ){
timSht35Out = TIME_OFF;
mode = SHT35_MEASURE;
timSht35start = TIME_SHT35_MAX;
}
}
/* SHT35へコマンド送出 */
void Sht35Measure(uint8_t dev_id,uint8_t* cmd, uint16_t len){
Wire.beginTransmission(dev_id); //スレーブが存在するか確認
byte error = Wire.endTransmission();
if( error == 0){ //スレーブが存在する場合下の処理
Wire.beginTransmission(dev_id);
for( uint16_t i=0; i < len; i++ ){
Wire.write(*cmd); //lenサイズ分だけデータを書き込む
++cmd;
}
Wire.endTransmission(); //ストップ・コンディションの発行
}
}
/* SHT35からデータを取得 */
bool Sht35GetData(uint8_t dev_id,uint8_t* reg_data, uint16_t len){
bool ret = false;
Wire.beginTransmission(dev_id); //スタート・コンディションの発行
byte error = Wire.endTransmission();
if( error == 0){ //スレーブが存在する場合下の処理
if( Wire.requestFrom(dev_id, len) == len ){
for( uint16_t i=0; i < len; i++ ){
*reg_data = Wire.read(); //len分だけデータをリードする
++reg_data;
}
ret = true;
}
}
return ret;
}
/* CRC8計算関数 */
uint8_t Crc8Calc(uint8_t *data, uint8_t sz ){
uint8_t crc = 0xFF;
uint8_t i,j;
for( i = 0; i < sz; i++){
crc ^= *data;
for( j = 0; j < 8; j++ ){
if( crc & 0x80 ){
crc = ( crc << 1 ) ^ POLYNOMIAL;
}
else{
crc = crc << 1;
}
}
++data;
}
return crc;
}
/* サーバー処理 */
void WifiSeverMain(void){
WiFiClient client = server.available();
if( client ){
String str = "";
Serial.println("New Client.");
while(client.connected()){
if(client.available()){
char c = client.read();
Serial.write(c);
if( c == '\n'){ //改行コード 0x0A
if( str.length() == 0 ){ //クライアントからの文字列が終わった後、以下の処理
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println();
client.print(HtmlSet()); //Htmlを送信
client.println();
client.stop(); //クライアント接続を停止
Serial.println("Client Disconnected.");
}else{
str = "";
}
}
else if( c != '\r'){ //復帰コード 0x0D
str += c;
}
}
}
}
}
/* クライアントに表示するhtml*/
String HtmlSet(void){
String str = "";
str += "<html lang=\"ja\">";
str += "<head>";
str += "<meta http-equiv=\"refresh\" content=\"5\">";
str += "<meta charset=\"UTF-8\">";
str += "<title>Sensor SHT35</title>";
str += "</head>";
str += "<body>";
str += "<h1>ESP32-SHT35-DIS温湿度センサ</h1>";
str += "<h2>温度: ";
str += temp;
str += "℃";
str += "</h2>";
str += "<h2>湿度: ";
str += humi;
str += "%RH";
str += "</h2>";
str += "</body>";
str += "</html>";
return str;
}
関連リンク
Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
PR:アクセンチュアの転職なら【コンサルアクシスコンサルティング】
最後まで、読んでいただきありがとうございました。
スマホにデータが表示されるとIoT機器を操作できてることを実感することができますが、表示がチープになってしまうのでHTML言語を使って見栄えをよくするなど必要だと感じました。