オフィス換気のため Co2 センサー作り ー Arduino, BME280, MH-Z19編

今年冬が寒くて窓が開けられない日が多く、作業中に換気のタイミングがなかなかわかりにくい。
それを解決するにはCO2センサーだと思いました。

早速CO2センサー購入しようと、調べてみると通販で販売しているものが一番安くても3万円弱だったので、
自分でCO2センサー作ると考えて始まりました。

まずはもっている材料から試作です。

2012年にmiyamomoさんの記事を読んでMZK-RP150Nとeneloop買いました。
http://miyamomonikkie.blog.so-net.ne.jp/2012-12-29
早速Arduino UNO、Ethernet Shield、MZK-RP150NとLM35D繋げて試してみました。

Arduino uno

いい感じで数字をテストサーバーにPOSTできるになりました。

ここまで試作がうまくできたので、本番用のCO2センサー探し始めました。
CO2センサーって意外と値段高いもので、一旦あきらめたんですが、
あの日偶然で上田さんの記事から中国で安いセンサーを販売してる店のリンクを紹介していたので、同じMH-Z19を買いました。
http://qiita.com/UedaTakeyuki/items/c5226960a7328155635f
試作でLM35D使ってみたんですが気圧も測りたくて、調べたら温度、湿度と気圧3つ測定できるのBME280を選びました。

繋げるとこういう感じでした。
Arduino BME280 MH-Z19

MH-Z19はUARTとPWM両方できます、今回は簡単なUARTでTXとRXを繋げます。
BME280はI2Cで繋げるので、ArduinoのA5(SCL)とA4(SDA)を繋げます。

Arduinoソース

MH-Z19は普通にSerial 9600bandから読めます。
使い方はネットでダウンロードできますが、簡単に説明すると、9 byteコマンド送って、レスポンスが戻ってきます。
0x86はreadコマンドで、戻るときも確認用のため0x86も一緒に返します。

例です

#include 
SoftwareSerial co2Serial(A0, A1); // define MH-Z19 RX TX

void setup() {
  co2Serial.begin(9600);
}

void loop() {
  byte cmd[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79};
  unsigned char response[9]; // for answer
  co2Serial.write(cmd, 9); //request PPM CO2
  co2Serial.readBytes(response, 9);
  if (response[0] != 0xFF){
    return 0;
  }
  if (response[1] != 0x86){
    return 0;
  }
  unsigned int responseHigh = (unsigned int) response[2];
  unsigned int responseLow = (unsigned int) response[3];
  int ppm = (256 * responseHigh) + responseLow;
  delay(60000);
}

BME280の場合もうできてあるライブラリがあって、ここでダウンロードできます。
http://static.cactus.io/downloads/library/bme280/cactus_io_BME280_I2C.zip
細かく説明はこちらになります。
http://cactus.io/hookups/sensors/barometric/bme280/hookup-arduino-to-bme280-barometric-pressure-sensor

#include 
#include "cactus_io_BME280_I2C.h"

BME280_I2C bme(0x76); // I2C using address 0x76

void setup() { 
  bme.begin();
  bme.setTempCal(-1); // Temp was reading high so subtract 1 degree
} 

void loop() { 
  bme.readSensor(); 
  bme.getPressure_MB(); 
  bme.getHumidity();
  bme.getTemperature_C();
  bme.getTemperature_F();
  delay(60000);
}

両方組み合わせでサーバーに送るソースはこれです。

#include 
#include 
#include 
#include "cactus_io_BME280_I2C.h"
#include 

BME280_I2C bme(0x76);  // I2C using address 0x76
SoftwareSerial co2Serial(A0, A1); // define MH-Z19 RX TX

byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};

EthernetClient client;
char server[] = "mochi.space";

void setup() {
  // give the ethernet module time to boot up:
  delay(1000);
  Ethernet.begin(mac);

  co2Serial.begin(9600);
  bme.begin();
  bme.setTempCal(-1);
}

void loop() {
    httpRequestSendParams();
    delay(30000);
}

void httpRequestSendParams() {
  // close any connection before send a new request.
  // This will free the socket on the WiFi shield
  client.stop();

  if (client.connect(server, 80)) {

    bme.readSensor(); 

    client.println("GET /bot/index.php?t="+String(bme.getTemperature_C(),3)+"&h="+String(bme.getHumidity(),3)+"&p="+String(bme.getPressure_MB(),3)+"&c="+String(readCO2())+" HTTP/1.1");
    client.println("Host: mochi.space");
    client.println("User-Agent: mochibot");
    client.println("Connection: close");
    client.println();
  } else {
    delay(10000);
  }
}

int readCO2(){
  byte cmd[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79};
  unsigned char response[9]; // for answer
  
  co2Serial.write(cmd, 9); //request PPM CO2
  co2Serial.readBytes(response, 9);
  
  if (response[0] != 0xFF){
    return 0;
  }
  if (response[1] != 0x86){
    return 0;
  }
  
  unsigned int responseHigh = (unsigned int) response[2];
  unsigned int responseLow = (unsigned int) response[3];
  int ppm = (256 * responseHigh) + responseLow;
  
  return ppm;
}

サーバーサイドでd3.jsと組み合わせ、こういう風にみえます。
d3.js