主要変更点
- 第一弾からハードウェア的にセンサをBME280 からBME680 へ変更し、照度センサ(BH1750FVI)も追加した分の修正・変更
- WEBアクセスの安定性を目的に、2CPUコアの一つをWEB処理専用に別スレッドで処理するようにした
- 全体的に記述方式を統一し、日本語コメントを多数追加した
実際のコードは下記の通り
/* roomstat2.ino
// コメントが %%% で囲まれた行は、環境毎に必ず変更が必要
// コメントが %% で囲まれた行は、要確認(環境毎に変更可)
*/
/*
//
// Global Definitions
//
*/
#include <Wire.h>
/*
// WiFi Network
*/
#include <WiFi.h>
#include <WiFiClient.h>
const char* ssid = "xxxxxxxx"; // %%% Wifi SSID %%%
const char* password = "xxxxxxxxxx"; // %%% Wifi password %%%
IPAddress ip(192, 168, 1, 48); // %% 固定IPアドレス設定 %%
IPAddress gateway(192,168, 1, 1); // %% ゲートウェイアドレス %%
IPAddress subnet(255, 255, 255, 0); // サブネットマスク
IPAddress DNS(192, 168, 1, 1); // %% DNSアドレス %%
/*
// Web Server
*/
#include <WebServer.h>
WebServer server(8090); // %% ポート番号 %%
const char* web_title = "RoomStat2_WEB"; // %% WEBタイトル名 %%
/*
// Ambient
*/
#include "Ambient.h"
unsigned int channelId = 10000; // %%% AmbientのチャネルID %%%
const char* writeKey = "xxxxxxxxxxxxxxxx"; // %%% ライトキー %%%
WiFiClient client;
Ambient ambient;
/*
// Multicore サポート
*/
#include "Arduino.h"
TaskHandle_t Task0;
TaskHandle_t Task1;
SemaphoreHandle_t semaphore; // 今回は使用しなかったが、排他制御実装時に使うべき
/*
// BH1750FVI sensor (照度センサ)
*/
int BH1750_address = 0x23; // i2c Addresse(OPEN or connecting to GND)
//int BH1750_address = 0x5c; // i2c Addresse(connecting to Vcc)
/*
// BME680 Sensor (Temperature, Humidity, Pressure, Gas)
*/
#include <Adafruit_Sensor.h>
#include "Adafruit_BME680.h"
Adafruit_BME680 bme; // BME680インスタンスの作成
/*
// OLED SSD1306 (有機LEDディスプレー)
*/
#include "SSD1306.h"
const int sda = 21;
const int scl = 22;
SSD1306 display(0x3c, sda, scl); // SSD1306インスタンスの作成
/*
//
// Custom Global Variables
//
*/
// 計測値保持グローバル変数
float room_temp = 0; // 温度データ
float room_humi = 0; // 湿度データ
float room_prss = 0; // 気圧データ
float room_gas = 0; // ガスデータ
float room_lux = 0; // 照度データ
/*
//
// Custom Routines
//
*/
//--- 照度データを読み取り返す
float get_lux(){
float valf=0;
byte i=0;
byte buff[2];
Wire.beginTransmission(BH1750_address);
Wire.requestFrom(BH1750_address, 2);
while(Wire.available()){
buff[i] = Wire.read();
i++;
}
Wire.endTransmission();
if(i==2){
valf=((buff[0]<<8)|buff[1])/1.2;
}
return valf;
}
//--- OLEDディスプレーに第1引数文字列を表示し、第2引数秒表示を保持
void ssd_display_and_wait(char* d_msg, int w_sec){
display.clear(); // フレーム消去
display.drawString(0, 0, d_msg); // フレーム描画
display.display(); // 画面表示実行
delay(w_sec*1000); // 画面表示保持
}
//--- WEB (/data)リクエスト対応 (主にmunin-node 用で、テキスト形式で返送)
// 返送形式 (munin-node では行3以降をグラフ化する)
// 行1: 日付
// 行2: 一行で全値表示 (目視確認用)
// 行3: 温度値 (有効桁数3)
// 行4: 湿度値 (有効桁数3)
// 行5: 気圧値 (有効桁数4)
// 行6: ガス値 (有効桁数4)
// 行7: 照度値 (有効桁数4)
void handleData() {
char dt_msg[20]; // 日時文字列格納用
char wb_msg[200]; // WEB 返信文字列格納用
struct tm timeInfo; // 時刻を格納する構造体
// 日時情報の取得
getLocalTime(&timeInfo);
sprintf(dt_msg, "%04d/%02d/%02d %02d:%02d:%02d",
timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday,
timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
// データ返送文字列作成 (munin-node に渡すデータはあえて有効桁数を減らしている)
sprintf(wb_msg, "%s\n" \
"Room Temrerature=%2.2f *C," \
"Room Humidity=%2.2f %%," \
"Atmospheric Pressure=%4.2f hPa," \
"Gas=%3.2f Kohms," \
"Illumination=%4.2f lx\n" \
"%2.1f\n%2.1f\n%4.0f\n%3.1f\n%4.0f\n",
dt_msg,
room_temp, room_humi, room_prss, room_gas, room_lux,
room_temp, room_humi, room_prss, room_gas, room_lux);
// WEB TEXT 返送
server.send(200, "text/plain", wb_msg);
// この関数が呼ばれる度に(通常munin-nodeから5分間隔)NTP 日時調整
configTime(9 * 3600L, 0, "ntp.nict.jp", "time.google.com", "ntp.jst.mfeed.ad.jp");
}
//--- WEB (/)リクエスト対応
void handleRoot() {
char dt_msg[20]; // 日時文字列格納用
char wb_msg[400]; // WEB 返信文字列格納用
struct tm timeInfo; // 時刻を格納する構造体
// 日時情報の取得
getLocalTime(&timeInfo);
sprintf(dt_msg, "%04d/%02d/%02d %02d:%02d:%02d",
timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday,
timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
// HTML 構文構築
sprintf(wb_msg, "<html>\n" \
"<head><title>%s</title></head>\n" \
"<body>\n" \
"<h1>%s</h1>\n" \
"<h2>Temperature=%2.2f *C</h2>\n" \
"<h2>Humidity=%2.2f %%</h2>\n" \
"<h2>Pressure=%4.2f hPa</h2>\n" \
"<h2>Gas=%3.2f Kohms</h2>\n" \
"<h2>Illumination=%4.2f lx</h2>\n" \
"</body>\n" \
"</html>\n",
web_title,
dt_msg,
room_temp, room_humi, room_prss, room_gas, room_lux);
// WEB HTML 返送
server.send(200, "text/html", wb_msg);
}
//--- WEB 登録URL以外(Not Found)対応
void handleNotFound() {
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i = 0; i < server.args(); i++) {
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
server.send(404, "text/plain", message);
}
//--- コア0ループ センサー読み出し&OLED表示更新 (同期処理、スケジュールド・タスク)
void loop_core0(void * parameter) {
struct tm timeInfo; // 時刻を格納する構造体
char dt_msg[20]; // 日時文字列格納用
char dp_msg[100]; // ディスプレー表示文字列格納用
const int Ambient_send_interval = 20; // Ambient へデータ送付間引き間隔
while(1){ // 無限イベントループ
for (int int_v = Ambient_send_interval; int_v > 0; int_v--) {
// BME680センサより温度データ、湿度データ、気圧データ、ガスデータ取得
bme.performReading();
room_temp = bme.temperature;
room_humi = bme.humidity;
room_prss = bme.pressure / 100.0f;
room_gas = bme.gas_resistance / 1000.0f;
room_lux = get_lux(); // 照度情報の取得
getLocalTime(&timeInfo); // 日時情報の取得
// $$$Display1 - 日時情報をOLEDディスプレーに表示
sprintf(dt_msg, "%04d/%02d/%02d\n%02d:%02d:%02d",
timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday,
timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
ssd_display_and_wait(dt_msg, 3);
// $$$Display2 - 温度&湿度情報をOLEDディスプレーに表示
sprintf(dp_msg, "Temp:%2.2f\nHumd:%2.2f\n",room_temp,room_humi);
ssd_display_and_wait(dp_msg, 4);
// $$$Display3 - 気圧&ガス情報をOLEDディスプレーに表示
sprintf(dp_msg, "P:%4.2f\nG:%3.1f\n",room_prss,room_gas);
ssd_display_and_wait(dp_msg, 4);
// $$$Display4 - 照度情報をOLEDディスプレーに表示
sprintf(dp_msg, "LUX:%4.0f\n", room_lux);
ssd_display_and_wait(dp_msg, 4);
// Ambient_send_inerval ループ内1回だけAmbient へ情報を送付する
if (int_v == 1){
// 温度、湿度、気圧、ガス、照度の値をAmbientに送信する (データー番号、データー値)
ambient.set(1, room_temp); // 温度
ambient.set(2, room_humi); // 湿度
ambient.set(3, room_prss); // 気圧
ambient.set(4, room_gas); // ガス
ambient.set(5, room_lux); // 照度
ambient.send();
}
} // for ループ閉じ
} // while ループ閉じ
}
//--- コア1ループ WEBリクエスト処理 (非同期処理)
void loop_core1(void * parameter) {
while(1){ // 無限イベントループ
server.handleClient();
}
}
/*
//
// Default Routines (setup & loop)
//
*/
//--- 標準イニシャル・セットアップ
void setup(void) {
Serial.begin(115200); // シリアルコンソールの速度設定
bool status;
//******* 初期化開始メッセージをシリアルコンソールへ表示
Serial.println("Setup started.");
//******* SSD OLED セットアップ
Serial.print("Checking OLED display... ");
status = display.init(); // OLEDディスプレイを初期化
if (!status) {
Serial.println("Could not find a valid OLED display, check wiring!");
while (1);
} else {
Serial.println("OK");
}
// OLEDへ初期化状況表示
display.clear(); // フレーム消去
display.setFont(ArialMT_Plain_24); // フォント設定
display.drawString(0, 0, "Initializing...\n.........\n");
display.display(); //指定された情報を描画
delay(10);
//******* BH1750セットアップ
Wire.begin();
Wire.beginTransmission(BH1750_address);
Wire.write(0x10); // 1 [lux] aufloesung
Wire.endTransmission();
Serial.println("BH1750 started...");
//******* BME680セットアップ
Serial.print(F("Checking BME680 ... "));
bme.begin(); // BME680を初期化
Serial.println("OK");
// Set up oversampling and filter initialization
bme.setTemperatureOversampling(BME680_OS_8X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_4X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320, 150); // 320*C for 150 ms
delay(150);
bme.performReading(); // 一回空読み込みを実施
//******* WiFiセットアップ
Serial.println("Setting up Wifi and WEB server");
WiFi.config(ip, gateway, subnet, DNS); // Set fixed IP address
delay(10);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password); // WiFi接続開始
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print(" - Connected to ");
// WiFi 接続情報シリアル出力
Serial.println(ssid);
Serial.print(" - IP address: ");
Serial.println(WiFi.localIP());
Serial.println(".");
//******* NTP時間調整
configTime(9 * 3600L, 0,
"ntp.nict.jp", "time.google.com", "ntp.jst.mfeed.ad.jp");
Serial.println("NTP time adjusted.");
//******* WEB サービス開始
server.begin();
Serial.println("WEB server started.");
// WEB リクエストマッピング設定
server.on("/", handleRoot);
server.on("/data", handleData);
server.on("/inline", []() {
server.send(200, "text/plain", "this works as well");
});
server.onNotFound(handleNotFound);
//******* Ambient 接続初期化
Serial.print("Connecting to Ambient... ");
// チャネルIDとライトキーを指定してAmbientクラウドサービス接続
status = ambient.begin(channelId, writeKey, &client);
if (!status) {
Serial.println("Ambient connection failed, check ID and Key");
while (1);
} else {
Serial.println("OK");
}
//******* 初期化完了メッセージをシリアルコンソールへ表示
Serial.println("Completed initial setup.");
//******* マルチコア使用の為のセットアップ
// ここで各コアにタスクを振り分ける
//semaphore = xSemaphoreCreateMutex(); // MUTually EXclusive
//semaphore = xSemaphoreCreateBinary();
// コア0で関数loop_core0をstackサイズ8000,優先順位1で起動
xTaskCreatePinnedToCore(loop_core0, "Task0", 8000, NULL, 1, NULL, 0);
delay(10);
// コア1で関数loop_core1をstackサイズ8000,優先順位2で起動
xTaskCreatePinnedToCore(loop_core1, "Task1", 8000, NULL, 2, NULL, 1);
Serial.println("Started processing with 2 cores.");
}
//--- 標準メイン・イベント・ループ
void loop(){
delay(1);
}