08.Basicのデータをスマホに表示(同期)
08.Basicのデータをスマホに表示(同期)
Basicで測定した温湿度&気圧をスマホに表示します。BasicをWebサーバーにして、同期サーバーにより表示します。表示の更新はスマホをスワイプします。次回は非同期の予定です。
http://marchan.e5.valueserver.jp/cabin/comp/jbox/arc214/doc21402.htmlを修正しています。
スマホ画面(測定タイミングは他と異なります)
生文字列リテラル
C++11以前では、char* kJsonData = "{\"data\": \"Hello World\"}";
の表記とすれば、C++11にある生文字列リテラルを使用すると
char* kJsonData = R"({"data": "Hello World"})";
と書けます。これは、R"(*****)" で表され、*****の中では " や \ などの文字をエスケープなしで用いることができます。改行文字等の特殊文字も書いた通りに解釈してくれます。ここではこれを使用します。
例) char* kJsonData = R"({
"data": "Hello World"
})";
は、char* kJsonData = "{\n \"data\": \"Hello World\"\n}" と同じです。
上記の*****の中に )"は入れられませんが、その時は終端部分に追加文字列を設定します。
例) char* kTestData = R"TEST("("+")")TEST";
これは、"("+")" と同じです。
HTML
HTTPヘッダー部
"HTTP/1.1 200 OK\r\n HTTPバージョン1.1 正常応答Content-Type:text/html\r\n" メディアタイプ:HTML文書
"Connection:close\r\n\r\n"; コネクションを切断
htmlヘッダー部・htmlボディー部
* 注釈を追加しています。
<!DOCTYPE HTML> <!-- HTML5.1 -->
<html lang="ja"> <!-- 日本語 -->
<html> <!-- html開始 -->
<head> <!-- 情報開始 -->
<meta charset="utf-8"> <!-- 文字コード -->
<meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 表示領域 画面幅,ズーム倍率 -->
<style> /* css開始 */
html{font-family:Helvetica;display:inline-block;margin:0px auto;text-align:center;}/*フォント名;インラインブロック;枠線からの距離;行揃え*/
h1 {font-size:28px;} /* h1の{文字サイズ} */
body {text-align:center;} /* bodyの{行揃え} */
table {border-collapse:collapse; margin-left:auto; margin-right:auto;} /* 枠線:間隔を開けない;中央寄せ */
th {padding:12px; background-color:#0000cd; color:white; border:solid 2px #c0c0c0;} /* 青地に白字等*/
tr {border:solid 2px #c0c0c0; padding:12px;} /* 枠線:実線 太さ 色;枠線からの距離 */
td {border:solid 2px #c0c0c0; padding:12px;} /* tdは... */
.value {color:blue; font-weight:bold; padding:1px;} /* class=valueは{文字色;太さ;距離;} */
</style> <!-- css終了 -->
</head> <!-- 情報終了 -->
<body> <!-- 表示開始 -->
<h1>%PAGE_TITLE%</h1> <!-- 見出し(1-6) 最大 -->
<p style="color:brown; font-weight:bold">測定値の更新は[再読込み]をクリック</p> <!-- 文字色;太さ -->
<table> <!-- 表開始 -->
<tr><th>項目</th><th>測定値</th></tr> <!-- 表の見出し -->
<tr><td>温度</td><td><span class="value">%TEMPERATURE%</span></td></tr> <!-- 表のデータ 温度 -->
<tr><td>湿度</td><td><span class="value">%HUMIDITY%</span></td></tr> <!-- 表のデータ 湿度 -->
<tr><td>気圧</td><td><span class="value">%PRE%</span></td></tr> <!-- 表のデータ 気圧 -->
</table> <!-- 表終了 -->
</body> <!-- 表示終了 -->
</html> <!-- html終了 -->
スケッチの解説
実行開始してwifi接続後にシリアルモニターにIPアドレスが表示されます。(例 192.168.68.67)スマホ(or PC)のWebブラウザーのアドレスバーに http://192.168.68.67 を入力すると測定値が表示されます。表示後はクライアントとの接続が切断されます。
ブラウザからの接続によって、一回だけ測定値を表示して動作を完結します。表示を更新する時は、スワイプ(PCではブラウザーの更新ボタンをクリック)することで再度リクエストを送信します。
PC画面(測定タイミングは他と異なります)
loop内にあるstrBufferは受信した文字列ですが、ここでは利用していません。currentLineは、1行文の受信文を格納するラインバッファで終端を検出しています。
loop最初で受信するまで待ちます。
指定したIPアドレスの再読込みをすると色々送られてきます(下記シリアルモニタ画面参照)が、スマホから"\r\n"の空白行が届くことでリクエストの終端を検出しています。
シリアルモニタ画面
新規クライアント GET /favicon.ico HTTP/1.1
Host: 192.168.68.67
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Referer: http://192.168.68.67/
Accept-Encoding: gzip, deflate
Accept-Language: ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7
19.3℃ 47% 1018hPa クライアントを切断
終端を検出後、温湿度&気圧を読取りhtmlを送信します。
"\r\n"の検出は、'\r'以外の文字はラインバッファに繋いでいます。'\n'を受信するたびにラインバッファをクリアしているので、読んだ文字が'\n'でラインバッファが空なら、クライアントからのリクエストが終了です。
最終的に受信した文のすべてが収納されているstrBufferをもとにHTTPリクエスト処理のhttpRequestProccess(String*)で受信データの解析と処理を行えますが、今回はスマホからのデータを受信して処理はないので、HTTPリクエスト処理はありません。
スマホでの表示動作
1.センサー値を読取る2.httpヘッダー部を送信
3.htmlヘッダー部を送信
4.htmlのボディー部のデータ値を置換して送信
変更部分は両側を%で挟んだプレースホルダーとして設定しておきます。テンプレートを作業領域に複写した後、プレースホルダーを対応するデータ値に変換します。
5.終端の空行を送信
です。
Basic画面(測定タイミングは他と異なります)
スケッチ
* 2ヶ所の*****は自分のwifiのssidとパスワードを記入してください。
// WiFiMeasurement.ino Basic用
// http://marchan.e5.valueserver.jp/cabin/comp/jbox/arc214/doc21402.html を修正
// BasicをWebサーバーにして、シリアルモニタに表示されたIPアドレスが 192.168.68.67 なら
// スマホ(クライアント)で http://192.168.68.67 を開くと温湿度&気圧を表示します。
// 更新はブラウザの更新です。PCでも表示します。
#include <M5Stack.h> // M5Stack Basicを使用
#include<WiFi.h> // wifiを使用
const char *ssid = "*****"; // 自分のネットワークのssid
const char *password = "*****"; // そのパスワード
#define LGFX_AUTODETECT // LovyanGFX自動認識
#define LGFX_USE_V1 // LovyanGFX Ver1を使用
#include<LGFX_AUTODETECT.hpp> // クラス"LGFX"を用意
static LGFX lcd; // LGFXのインスタンスを作成
#include<Wire.h> // I2Cを使用
#include<Adafruit_SHT31.h> // 温湿度センサを使用
#include<Adafruit_BMP280.h> // 気圧センサを使用
Adafruit_SHT31 sht = Adafruit_SHT31(&Wire); // sht定義
Adafruit_BMP280 bme = Adafruit_BMP280(&Wire); // bmp定義だがbmeとする
WiFiServer server(80); // デフォルトのhttpポートを使用
String strTmp, strHum, strPre; // 単位付測定値
const String strResponseHeader = "HTTP/1.1 200 OK\r\nContent-Type:text/html\r\n"
"Connection:close\r\n\r\n"; // httpヘッダー
const String strHtmlHeader = R"rawliteral(
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html {font-family:Helvetica; display:inline-block; margin:0px auto; text-align:center;}
h1 {font-size:28px;}
body {text-align:center;}
table {border-collapse:collapse; margin-left:auto; margin-right:auto;}
th {padding:12px; background-color:#0000cd; color:white; border:solid 2px #c0c0c0;}
tr {border:solid 2px #c0c0c0; padding:12px;}
td {border:solid 2px #c0c0c0; padding:12px;}
.value {color:blue; font-weight:bold; padding:1px;}
</style>
</head>
)rawliteral"; // htmlページのヘッダー
const String strHtmlBody = R"rawliteral(
<body>
<h1>%PAGE_TITLE%</h1>
<p style="color:brown; font-weight:bold">更新は[再読込]をクリック</p>
<table>
<tr><th>項目</th><th>測定値</th></tr>
<tr><td>温度</td><td><span class="value">%TEP%</span></td></tr>
<tr><td>湿度</td><td><span class="value">%HUM%</span></td></tr>
<tr><td>気圧</td><td><span class="value">%PRE%</span></td></tr>
</table>
</body></html>
)rawliteral"; // htmlページのボディー
void sokutei() { // センサー読取りとBasic画面表示関数
float tmp = sht.readTemperature() + 0.5; // センサー温度を読取る
float hum = sht.readHumidity() + 0.5; // センサー湿度を読取る
float pre = bme.readPressure() / 100.0 + 0.5; // センサー気圧を読取る
if (isnan(hum) || isnan(tmp) || isnan(pre)) { // 測定値が数値でなければ
Serial.println("失敗 センサー読込み"); // 表示
return;
}
char tmpT[7], humT[7], preT[7]; // 文字データ定義
dtostrf(tmp, 5, 1, tmpT); // double→文字列(数値,文字列長,小数点以下の桁数,文字列)
dtostrf(hum, 3, 0, humT); // 湿度データの文字列変換
dtostrf(pre, 4, 0, preT); // 気圧データの文字列変換
strTmp = String(tmpT) + "℃ "; // 単位も結合
strHum = String(humT) + "% "; // 単位も結合
strPre = String(preT) + "hPa"; // 単位も結合
Serial.printf("%s %s %s", strTmp, strHum, strPre); // 表示
lcd.fillScreen(WHITE); // 画面クリア
lcd.setCursor(0, 0); // カーソル指定
lcd.setTextColor(BLACK, WHITE); // (文字色{,背景色})
lcd.println(" 温湿度・気圧\n"); // 表示
lcd.setTextColor(WHITE, WHITE); // (文字色{,背景色})
lcd.print(" "); // 表示
lcd.setTextColor(WHITE, BLUE); // (文字色{,背景色})
lcd.print("項目"); // 表示
lcd.setTextColor(WHITE, WHITE); // (文字色{,背景色})
lcd.print(" "); // 表示
lcd.setTextColor(WHITE, BLUE); // (文字色{,背景色})
lcd.println("測定値"); // 表示
lcd.setTextColor(DARKGREY, WHITE); // (文字色{,背景色})
lcd.print(" 温度 "); // 表示 温度
lcd.setTextColor(BLUE, WHITE); // (文字色{,背景色})
lcd.println(strTmp); // 表示 温度
lcd.setTextColor(DARKGREY, WHITE); // (文字色{,背景色})
lcd.print(" 湿度 "); // 表示 湿度
lcd.setTextColor(BLUE, WHITE); // (文字色{,背景色})
lcd.println(strHum); // 表示 湿度
lcd.setTextColor(DARKGREY, WHITE); // (文字色{,背景色})
lcd.print(" 気圧 "); // 表示 気圧
lcd.setTextColor(BLUE, WHITE); // (文字色{,背景色})
lcd.println(strPre); // 表示 気圧
for (int i = 2; i < 7; i++) { // 5本
lcd.drawFastHLine(18, i * 36, 36 * 7 + 18, DARKGREY); // 水平線(x,y,w,color)
}
lcd.drawFastVLine(18, 36 * 2, 36 * 4, DARKGREY); // 垂直線(x,y,h,color)
lcd.drawFastVLine(36 * 3 + 18, 36 * 2, 36 * 4, DARKGREY); // 垂直線 中
lcd.drawFastVLine(36 * 8, 36 * 2, 36 * 4, DARKGREY); // 垂直線 右
}
void httpSendResponse(WiFiClient *client) { // http送信関数
sokutei(); // センサー読取りとBasic画面表示関数へ
client->println(strResponseHeader); // httpヘッダ部を送信 アロー演算子
client->println(strHtmlHeader); // ヘッダー部を送信
String buf = strHtmlBody; // ボディー部をbufに代入
buf.replace("%PAGE_TITLE%", "温湿度・気圧"); // bufの文字列置換1
buf.replace("%TEP%", strTmp); // bufの文字列置換2
buf.replace("%HUM%", strHum); // bufの文字列置換3
buf.replace("%PRE%", strPre); // bufの文字列置換4
client->println(buf); // bufを送信
client->println(); // http終端の空行を送信
}
void LCDset() { // 初期画面設定関数
lcd.init(); // 画面初期化
lcd.setRotation(1); // 回転方向(0-3)
lcd.setBrightness(20); // バックライト輝度(0-255)
lcd.setColorDepth(24); // RGB888 24bit (16=RGB565 16bit)
lcd.setCursor(0, 0); // 表示位置(x,y)
lcd.setFont(&fonts::lgfxJapanGothic_36); // ゴシック(8,12,16,20,24,28,32,36,40)
//lcd.setTextColor(GREEN, BLACK); // 色設定(文字色[,背景色])
lcd.setTextColor(BLACK, WHITE); // 色設定(文字色[,背景色])
}
void setup() {
M5.begin(); // M5stack初期化
Serial.begin(115200); // シリアルモニタ通信速度設定
LCDset(); // 初期画面設定関数へ
while (!bme.begin(0x76)) { // BMP280のアドレスで開始できない時
M5.lcd.println("エラー BMP280未接続"); // 表示
}
while (!sht.begin(0x44)) { // shtのアドレスで開始できない時
M5.lcd.println("エラー SHT3X未接続"); // 表示
}
WiFi.mode(WIFI_AP_STA); // AP(アクセスポイント)+STA(ステーション)モード
WiFi.begin(ssid, password); // ネットワーク設定を初期化
while (WiFi.status() != WL_CONNECTED) { // 接続されなかったら
delay(1000); // 1秒待つ (接続されるまで待つ)
Serial.println("wifiステーションとして設定中"); // 表示
}
Serial.print("IPアドレス: "); // 表示
Serial.println(WiFi.localIP()); // IPアドレスを取得し表示
Serial.print("wifiチャンネル: "); // 表示
Serial.println(WiFi.channel()); // wifiチャネルを取得し表示
server.begin(); // WiFiサーバを開始
sokutei(); // センサー読取りとBasic画面表示関数へ
}
void loop() {
String strBuffer = ""; // 受信文字列をクリア
WiFiClient client = server.available(); // wifiクライアントからの接続を取得
if (client) { // クライアントから着信があれば(ずっと待っている)
Serial.print(" 新規クライアント "); // 表示
String currentLine = ""; // ラインバッファをクリア
while (client.connected()) { // 接続している間は以下の処理
if (client.available()) { // 利用可能なバイト数があれば
char c = client.read(); // 受信データを1バイト読む
Serial.print(c); //表示
strBuffer += c; // 受信文字列に結合
if (c == '\n') { // もし改行なら
if (currentLine.length() == 0) { // (改行で)さらにラインバッファが空なら
httpSendResponse(&client); // 上記のHTTPレスポンス関数へ ここのみから
break; // whileループから抜ける
} else { // それ以外(ラインバッファが空でなく改行の時)なら
currentLine = ""; // ラインバッファをクリア
}
} else if (c != '\r') { // (改行と)復帰以外なら
currentLine += c; //ラインバッファに結合する
}
}
}
client.stop(); // (接続かなくなったら)TCP接続を切断
Serial.println(" クライアントを切断"); // 表示
}
}
* flash memory(2.0Mbyte)のうち、スケッチが65%使用。RAM(327kbyte)のうち、global変数が12%使用、local変数で287kbyte使用可能。(1000byte=1kbyteで計算)