自宅IoT 第二弾 – 4.1ソフトウェア (IoT、スケッチ)

投稿者: | 2020-02-29

主要変更点

  • 第一弾からハードウェア的にセンサを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);
}

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です