ESP-WROOM-02とBME280を使った温度測定に関係する基本的なソース類をまとめた。概要や回路については、こちらを参照のこと。

目次

  1. 改良前メインスケッチ(ESP_BME280-1.ino)
  2. センサーインタフェースクラス(hdc1000_bmc280.h)
  3. テーブル作成スクリプト(create_table.sql)
  4. サーバー側データベースインタフェースクラス(envdata_db.php)
  5. サーバー側データ格納スクリプト(store_data.php)

ESP_BME280-1.ino

消費電流を考慮していない当初のスケッチ。ESP8266 Arduino Core 2.4.0 を使ってビルド。

#include <ESP8266WiFi.h>
#include "hdc1000_bme280.h"
extern "C" {
  #include "user_interface.h"
}
#define START_MSG "\n" + String(__FILE__) + " start."
#if 1
#define SERIAL_INIT(a)  Serial.begin(a)
#define SERIAL_OUT(a) Serial.println(a)
#else
#define SERIAL_INIT(a)
#define SERIAL_OUT(a)
#endif
#define LED1 15
#define MEASURE_INTERVAL_SECONDS  180

bme280 bmp280(0x76);

// wifi
const char* ssid = "ssid";
const char* password = "password";
const char* remote_host = "host_ipaddress";

void die(int msec, const char* cp) {
  if (cp)
    SERIAL_OUT(cp);
  for(;;) {
    digitalWrite(LED1, 1);
    delay(msec);
    digitalWrite(LED1, 0);
    delay(msec);
  }
}

void deep_sleep(int seconds) {
  SERIAL_OUT("deep_sleep()");
  ESP.deepSleep(seconds * 1000 * 1000, WAKE_RF_DEFAULT);
  delay(10);
}
bool ap_connect() {
  SERIAL_OUT("\nstart ap_connect()");
  WiFi.mode(WIFI_STA);
  wifi_station_set_auto_connect(false);
  WiFi.begin(ssid, password); 
  delay(200);
  int n;
  int count = 0;
  int retry = 0;
  while ((n = WiFi.status()) != WL_CONNECTED) {
    SERIAL_OUT(String(n) + " ");
    delay(500);
    if (n == WL_NO_SSID_AVAIL || n == WL_CONNECT_FAILED) {
      WiFi.reconnect();
      count = 0;
      retry++;
    }
    if (count++ > 30 || retry > 10) {
      SERIAL_OUT("");
      return false;
    }
  }
  SERIAL_OUT("\nap_connect() done.");
  return true;
}

void ap_disconnect() {
  if (WiFi.status() != WL_CONNECTED)
    return;  
  WiFi.disconnect();
  int n;
  while ((n = WiFi.status()) != WL_IDLE_STATUS) {
    SERIAL_OUT(String(n) + " ");
    delay(50);
  }        
  SERIAL_OUT("ap_disconnect() done.");
}

bool wifi_send_data(float temp, float humi, float bpress, float vbat) {
  SERIAL_OUT("start wifi_send_data()");
  WiFiClient client;
  if (!client.connect(remote_host, 80))
    return false;
  String request = "/store_data.php?point_id=" + WiFi.macAddress() + "&T=" + String(temp) + "&H=" + String(humi) + 
      "&P=" + String(bpress) + "&V=" + String(vbat);
  String req_line = "GET " + request + " HTTP/1.1\r\nHost: " + String(remote_host) + "\r\nConnection: close\r\n\r\n";
  client.print(req_line);
  int count = 0;
  while(!client.available()) {  // waif for response.
    delay(50);
    count++;            
      if (count >= 200) {  // limit = 10secs.
        return false;
      }
  }
  while(client.available()){
    String line = client.readStringUntil('\n');
    SERIAL_OUT(line);
    delay(1);
  }
  return true;  
}

void setup() {
  delay(10);
  SERIAL_INIT(115200);
  SERIAL_OUT(START_MSG);
  
  pinMode(LED1, OUTPUT);
  digitalWrite(LED1, 1);
  if (!bmp280.init())
    die(500, "FATAL: BMP280 init failed.");
  if (!ap_connect()) {
      deep_sleep(30);  // reset myself after a  while.
      return;
  }
  float vbat = 570.0 * system_adc_read() / 1024.0;  // 100倍した電圧。470K + 100Kでの分圧時 570 / 100 = 5.7
  bmp280.measure();
  SERIAL_OUT("\nPa = " + String(bmp280.press) + ", Temp = " + String(bmp280.temp) + ", Humi = " + String(bmp280.humi) + ", Vbat = " + String(vbat));
  wifi_send_data(bmp280.temp, bmp280.humi, bmp280.press, vbat);
  ap_disconnect();
  deep_sleep(MEASURE_INTERVAL_SECONDS);
}

void loop() {
  delay(100);
}

解説

このスケッチは、下に載せたセンサーインタフェース用のhdc1000_bme280.h と同じフォルダに入れておく。そうすることで、Arduino IDEを開いたときに各ファイルをタブで選択できるようになる。

定義、定数関係

スケッチを作るときはシリアルモニタにいろいろなメッセージを表示させるが、実稼働時には不要になのでSerial を使う部分は#if ~ #endifブロックに囲んだ。

#define MEASURE_INTERVAL_SECONDS 180 は、測定間隔の秒数を定義しており、1000000倍して ESP.deepSleep(); に渡す。

bme280 bmp280(0x76);  によってbme280クラスのインスタンスを作成しており、引数の0x76でi2cアドレスを指定している。このアドレスに接続されているセンサーがBME280なのかBMP280なのかの識別はbme280クラス内で行っている。このスケッチは、以前BMP280用に作ったものを流用したので変数名がbmp280になっている。

ssid, password, remote_hostは、接続先のWiFiアクセスポイントに接続するための情報と、データ送信先のリモートホストのアドレスを指定している。

void deep_sleep(int seconds)

ディープスリープする秒数を指定する。この関数内では、 ESP.deepSleep(seconds * 1000 * 1000, WAKE_RF_DEFAULT);  を呼ぶことで、スリープ時間をμ秒単位にして渡している。この関数が呼び出す EspClass::deepSleep()メソッドは、スリープ時間を 32ビットの符号なし整数で受けるから、最大で UINT_MAX μ秒まで指定可能となる。具体的には約4295秒 ≒ 71.6分まで指定可能ということになる。

なお、EspClass::deepSleep() 内では、system_deep_sleep(); というSDKのAPIに受け取った引数をそのまま渡している。SDK 側は 64ビットの符号なし整数を受け付けるので、はるかに長い時間を指定することができる。現段階では Arduino Core 2.4を利用しているのでuint32_tでディープスリープ時間を指定する必要があるが、将来的には uint64_tで指定できるようになるのかもしれない。

ESP8266 Arduino Core 2.4.1 において、ESP.deepSleep() の引数は uint64_t  に変更された。https://github.com/esp8266/Arduino/releases を参照。

bool ap_connect()

WiFiアクセスポイントに接続するための関数。以前から疑いもなく使っているが、接続が完了するまで7~10秒ほどかかってしまう。動作の開始からdeep sleepに入るまでの時間のほとんどはこの関数が費やしてしまうので、電池のモチを考えると改善すべき部分。接続時間を3秒以下に短縮する方法については本文側に掲載した。

void ap_disconnect()

WiFiアクセスポイントから切断するための関数。やはり歴史的に使い続けている。

bool wifi_send_data(float temp, float humi, float  press, float vbat)

remote_host で指定のホストのポート80にtcp接続し、パラメータの温度、相対湿度、気圧および電池電圧をHTTP 1.1のGETリクエストで送信する。リクエスト内容は、/store_data.php?point_id=mac_addr&T=temp&H=humi&P=press&V=vbat  。

送信後、サーバーからのレスポンスを受け取りシリアルモニタに表示する。例えば、レスポンスヘッダやボディ内の文字列を解釈してボード側の動作を変更するようなこともあるので、丁寧に(?)受信している。

void setup()

まずはGPIO15に接続したLEDを点灯させ、BME280を初期化する。初期化に失敗した場合LEDを点滅させて停止する。続いてap_connect() でWiFiアクセスポイント(うちの場合は自宅のルーター)に接続するが、稀に接続が失敗することもあるので、その場合は30秒後に最初からやり直すことにしている。

続いて、system_adc_read() によってWROOM-02のTOUT端子に接続している分圧した電池電圧を測定し、分圧比を掛けて実電圧の100倍の値を得ている。電池電圧は、NiMH直列4本なので最大5.7Vを想定しており、内蔵ADCの分解能は1024ビットなので1ビットにつき0.0056Vを表していることになる。

そして、bmp280.measure() によって温度、相対湿度、気圧を得る。BME280のオーバーサンプリングは3対象ともx1としているので、測定時間は約10msecとしている。

得られた各値をシリアルモニタに表示し、wifi_send_data() を使ってサーバーに送る。最後に ap_disconnect() で切断し、deep_sleep(MEASURE_INTERVAL_SECONDS); によって次の測定タイミングまでdeep sleepする。

void loop() では何もしない。

hdc1000_bme280.h

#include <stdint.h>
#include "Wire.h"

class i2c_support
{
private:
  byte i2c_address;
public:
  i2c_support(byte address) {
    i2c_address = address;
    Wire.begin();
  }

// read 8bit data from pointer.
  uint8_t i2c_read8(byte pointer) {
    Wire.beginTransmission(i2c_address);
    Wire.write(pointer);
    Wire.endTransmission();
    Wire.requestFrom(i2c_address,  (byte)1);
    return (uint8_t)Wire.read();
  }

// read 16bit data from addr/pointer. 1st MSB, 2nd LSB.
  uint16_t i2c_read16(byte pointer) {
    Wire.beginTransmission(i2c_address);
    Wire.write(pointer);
    Wire.endTransmission();
    Wire.requestFrom(i2c_address,  (byte)2);
    unsigned short data = Wire.read();
    data <<= 8;
    data |= Wire.read();
    return data;
  }

// read 16bit data from addr/pointer. 1st LSB, 2ns MSB.
  uint16_t i2c_read16_swab(byte pointer) {
    Wire.beginTransmission(i2c_address);
    Wire.write(pointer);
    Wire.endTransmission();
    Wire.requestFrom(i2c_address,  (byte)2);
    uint16_t lsb = (uint16_t)Wire.read();
    uint16_t msb = (uint16_t)(Wire.read() << 8);
    return msb | lsb;
  }

// for HDC1000
  uint32_t i2c_read32(byte pointer, int delay_ms = 0) {
    Wire.beginTransmission(i2c_address);
    Wire.write(pointer);  // このタイミングでA/D変換が開始する。
    Wire.endTransmission();
    delay(delay_ms);
    Wire.requestFrom(i2c_address,  (byte)4);
    unsigned int data = Wire.read();
    data <<= 8;
    data |= Wire.read();
    data <<= 8;
    data |= Wire.read();
    data <<= 8;
    data |= Wire.read();
    return data;
  }

  void i2c_read_burst(byte pointer, int byte_count, byte* p) {
    Wire.beginTransmission(i2c_address);
    Wire.write(pointer);
    Wire.endTransmission();
    Wire.requestFrom(i2c_address,  (byte)byte_count);
    while(Wire.available() && byte_count > 0) {
      *p++ = (byte)Wire.read();
      byte_count--;
    }
  }

  void i2c_write_reg(byte reg, byte data) {
    Wire.beginTransmission(i2c_address);
    Wire.write(reg);
    Wire.write(data);
    Wire.endTransmission();
  }
};

class hdc1000 : public i2c_support
{
public:
  hdc1000() : i2c_support(0x40) {
    temp = 0.0;
    humi = 0.0;
  }
  enum { reg_measure = 0, reg_config = 2, reg_id = 0xff };
 
  bool init() {
    i2c_write_reg(reg_config, 0x90);  // 10000001B = RESET, 32bit MODE(temp << 16 + humi)
    return (i2c_read16(reg_id) == 0x1000); // check device id.
  }
  void measure() {
    uint32_t data = i2c_read32(reg_measure, 15);
    temp = (float)((data >> 16) / 65536.0) * 165.0 - 40.0;
    humi = (float)((data & 0xffff) / 65536.0) * 100.0;
  }
  float temp;
  float humi;
};

#ifndef BME280_S32_t
#define BME280_S32_t  int32_t
#endif
#ifndef BME280_U32_t
#define BME280_U32_t  uint32_t
#endif
#ifndef BME280_S64_t
#define BME280_S64_t  int64_t
#endif

class bme280 : public i2c_support
{
  bool bme280_flag;  // true : bme280, false: bmp280
  bool unknown_flag = false;
public:
  bme280(int address ) : i2c_support(address) {
    temp = 0.0;
    humi = 0.0;
    press = 0.0;
  }
  // results.
  float temp;
  float humi;
  float press;
  enum { Tmeasure = 10 };  // oversamplinkg, x1 x1 x1
/// registers.
  enum { reg_id = 0xd0, reg_reset = 0xe0, reg_ctrl_hum = 0xf2, reg_ctrl_meas = 0xf4, reg_config = 0xf5 };
  enum { reg_p_result = 0xf7, reg_t_result = 0xfa, reg_h_result = 0xfd };

// IIR Filter disable. 
// osrs_p = 3, osrs_t = 3 ---> 18bit either.
  bool init() {
    uint8_t id = i2c_read8(reg_id); // check device-id.
    if (id == 0x60)
      bme280_flag = true;
    else if (id == 0x58)
      bme280_flag = false;
    else
      return false;
    load_bme280_compensation_params();
    i2c_write_reg(reg_config, 0); //  no stand-by, no IIR filter, no SPI.
    return true;
  }

//  TEMP resolution = 16 + (osrs_t ? 1) bit, HUM resolution = 16 + (osrs_h ? 1) bit,
//  select forced mode.
  void measure()  {
    if (bme280_flag)
      i2c_write_reg(reg_ctrl_hum, 1);   // humi measurement control register.00000011  (osrs_h = 1);
    i2c_write_reg(reg_ctrl_meas, 0x25); // measurement control register. 00100101 (osrs_p = 1, osrs_t = 1, Forced mode);
    delay(Tmeasure);

    byte buffer[10];
    memset(buffer, 0, sizeof(buffer));
    i2c_read_burst(reg_p_result, 8, buffer);
    uint32_t raw_press = ((uint32_t)buffer[0] << 12) | ((uint32_t)buffer[1] << 4) | (((uint32_t)buffer[2] >> 4) & 0xf);
    uint32_t raw_temp = ((uint32_t)buffer[3] << 12) | ((uint32_t)buffer[4] << 4) | (((uint32_t)buffer[5] >> 4) & 0xf);
    uint16_t raw_humi = ((uint16_t)buffer[6] << 8) | (uint16_t)buffer[7];
    // uint16_t raw_humi = i2c_read16(reg_h_result);
#if 0
    Serial.print("raw_press = ");
    Serial.println(raw_press, HEX);
    Serial.print("raw_temp = ");
    Serial.println(raw_temp, HEX);
    Serial.print("raw_humi = ");
    Serial.println(raw_humi, HEX);
#endif
    temp = BME280_compensate_T_int32(raw_temp) / 100.0;
    press = BME280_compensate_P_int64(raw_press) / 25600.0;
    if (bme280_flag)
      humi = bme280_compensate_H_int32(raw_humi) / 1024.0;
    else
      humi = 0.0;
    // reset.
  //  i2c_write_reg(reg_reset, 0xb6);
  }

// from BST-BME280_DS001-10.pdf Chapter4.2.3 (rev.1.1)
private:
  BME280_S32_t t_fine;

// Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC.
// t_fine carries fine temperature as global value
  BME280_S32_t BME280_compensate_T_int32(BME280_S32_t adc_T) {
    BME280_S32_t var1, var2, T;
    var1 = ((((adc_T>>3) - ((BME280_S32_t)dig_T1<<1))) * ((BME280_S32_t)dig_T2)) >> 11;
    var2 = (((((adc_T>>4) - ((BME280_S32_t)dig_T1)) * ((adc_T>>4) - ((BME280_S32_t)dig_T1))) >> 12) * ((BME280_S32_t)dig_T3)) >> 14;
    t_fine = var1 + var2;
    T = (t_fine * 5 + 128) >> 8;
    return T;
  }

// Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 integer bits and 8 fractional bits).
// Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa
  BME280_U32_t BME280_compensate_P_int64(BME280_S32_t adc_P)  {
    BME280_S64_t var1, var2, p;
    var1 = ((BME280_S64_t)t_fine) - 128000;
    var2 = var1 * var1 * (BME280_S64_t)dig_P6;
    var2 = var2 + ((var1*(BME280_S64_t)dig_P5)<<17);
    var2 = var2 + (((BME280_S64_t)dig_P4)<<35);
    var1 = ((var1 * var1 * (BME280_S64_t)dig_P3)>>8) + ((var1 * (BME280_S64_t)dig_P2)<<12);
    var1 = (((((BME280_S64_t)1)<<47)+var1))*((BME280_S64_t)dig_P1)>>33;
    if (var1 == 0) {
      return 0; // avoid exception caused by division by zero
    }
    p = 1048576 - adc_P;
    p = (((p<<31) - var2)*3125) / var1;
    var1 = (((BME280_S64_t)dig_P9) * (p>>13) * (p>>13)) >> 25;
    var2 = (((BME280_S64_t)dig_P8) * p) >> 19;
    p = ((p + var1 + var2) >> 8) + (((BME280_S64_t)dig_P7)<<4);
    return (BME280_U32_t)p;
  }

// Returns humidity in %RH as unsigned 32 bit integer in Q22.10 format (22 integer and 10 fractional bits).
// Output value of “47445” represents 47445/1024 = 46.333 %RH
  BME280_U32_t bme280_compensate_H_int32(BME280_S32_t adc_H)  {
    BME280_S32_t v_x1_u32r;

    v_x1_u32r = (t_fine - ((BME280_S32_t)76800));
    v_x1_u32r = (((((adc_H << 14) - (((BME280_S32_t)dig_H4) << 20) - (((BME280_S32_t)dig_H5) * v_x1_u32r)) +
      ((BME280_S32_t)16384)) >> 15) * (((((((v_x1_u32r * ((BME280_S32_t)dig_H6)) >> 10) * (((v_x1_u32r * 
      ((BME280_S32_t)dig_H3)) >> 11) + ((BME280_S32_t)32768))) >> 10) + ((BME280_S32_t)2097152)) * 
      ((BME280_S32_t)dig_H2) + 8192) >> 14));
    v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((BME280_S32_t)dig_H1)) >> 4));
    v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r);
    v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r);
    return (BME280_U32_t)(v_x1_u32r>>12);
  }

// Chapter4.2.2. Trimming parameters.
  uint16_t  dig_T1;
  int16_t dig_T2;
  int16_t dig_T3;

  uint16_t  dig_P1;
  int16_t dig_P2;
  int16_t dig_P3;
  int16_t dig_P4;
  int16_t dig_P5;
  int16_t dig_P6;
  int16_t dig_P7;
  int16_t dig_P8;
  int16_t dig_P9;

  uint8_t dig_H1;
  int16_t dig_H2;
  uint8_t dig_H3;
  int16_t dig_H4;
  int16_t dig_H5;
  uint8_t dig_H6;

  void load_bme280_compensation_params() {
    dig_T1 = i2c_read16_swab(0x88);
    dig_T2 = (int16_t)i2c_read16_swab( 0x8a);
    dig_T3 = (int16_t)i2c_read16_swab(0x8c);

    dig_P1 = i2c_read16_swab(0x8e);
    dig_P2 = (int16_t)i2c_read16_swab(0x90);
    dig_P3 = (int16_t)i2c_read16_swab(0x92);
    dig_P4 = (int16_t)i2c_read16_swab(0x94);
    dig_P5 = (int16_t)i2c_read16_swab(0x96);
    dig_P6 = (int16_t)i2c_read16_swab(0x98);
    dig_P7 = (int16_t)i2c_read16_swab(0x9a);
    dig_P8 = (int16_t)i2c_read16_swab(0x9c);
    dig_P9 = (int16_t)i2c_read16_swab(0x9e);

    if (bme280_flag) {
      dig_H1 = i2c_read8(0xa1);
      dig_H2 = (int16_t)i2c_read16_swab(0xe1);
      dig_H3 = i2c_read8(0xe3);
      uint16_t _e4 = (uint16_t)i2c_read8(0xe4);
      uint16_t _e5 = (uint16_t)i2c_read8(0xe5);
      uint16_t _e6 = (uint16_t)i2c_read8(0xe6);
      dig_H4 = (int16_t)((_e4 << 4) + (_e5 & 0xf));
      dig_H5 = (int16_t)((_e6 << 4) + ((_e5 >> 4) & 0xf));
      dig_H6 = (int16_t)i2c_read8(0xe7);
    }
  }
};

内容については、以前の投稿を参照

テーブル作成スクリプト

mysql (5.1)用の、データベースおよびテーブル作成用スクリプト。

-- create database envdata default character set utf8 collate utf8_general_ci;
-- grant all privileges on envdata.* to envuser@localhost identified by 'xxxxxx';

use envdata;
create table envdata(
  serial_no  int  not null   AUTO_INCREMENT
  ,point_id varchar (20) not null
  ,T float (7,2) null
  ,H float (7,2) null
  ,P float (7,2) null
  ,V float (7,2) null
  ,X1 float (7,2) null
  ,X2 float (7,2) null
  ,post_datetime timestamp default CURRENT_TIMESTAMP not null
  ,PRIMARY KEY (serial_no)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
--
create table envdata2(
  serial_no  int  not null   AUTO_INCREMENT
  ,point_id varchar (20) not null
  ,BMP_T float (7,2) null
  ,BMP_P float (7,2) null
  ,HDC_T float (7,2) null
  ,HDC_H float (7,2) null
  ,V float (7,2) null
  ,X1 float (7,2) null
  ,X2 float (7,2) null
  ,post_datetime timestamp default CURRENT_TIMESTAMP not null
  ,PRIMARY KEY (serial_no)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
--

テーブルenvdataにはBME280単独のボードからのデータを、envdata2には、BMP280とHDC1000を載せたボードからのデータを格納する。Tが温度、Hが相対湿度、Pが気圧、Vが電圧で、X1およびX2は拡張用(接続時間の格納など)。

サーバー側データベースインタフェースクラス(envdata_db.php)

データベース envdbに接続し測定データの追加や、テーブルに格納されているデータのクエリを行うためのクラス。Linux上のPHP 5.33で動作を確認した。

<?PHP
class envdata_db {
	private $mysql;	// コネクション
	private $last_error;
	private $sql;
 
	public function __construct(){
		$this->mysql= mysqli_connect("localhost", "envuser", "xxxxxxx", "envdata");	
		$this->last_error = mysqli_connect_error();
	}
 
	public function __destruct() {
		if ($this->mysql)
			mysqli_close($this->mysql);
		$this->mysql = FALSE ;
	}
 
	public function isConnect() {
		return ($this->mysql != FALSE);
	}
 
	public function getLast_error() {
		return $this->last_error;
	}
 
	public function getLast_sql() {
		return $this->sql;
	}

	public function insert_array($table, $ar) {
		header('cotent-type: text/html');
        if (!$this->isConnect())
            return FALSE;
		foreach($ar as $k => $v) {
        	$s = isset($_GET[$k]) ? $_GET[$k] : "";
	        if (strlen($s) > 0)
    	            $ar[$k] = $s;
		}
		$cols = "";
		$vals = "";
		foreach($ar as $k => $v) {
		    if (strlen($cols) > 0)
        		$cols .= ",";
			$cols .= $k;
			if (strlen($vals) > 0)
	        	$vals .= ",";
		    $vals .= is_string($v) ? "'" . mysqli_real_escape_string($this->mysql, $v) . "'" : $v;
		}
		$this->sql = "insert into " . $table . "(" . $cols . ") values (" . $vals . ")";
        if (!mysqli_query($this->mysql, $this->sql)) {
            $this->last_error = mysqli_error($this->mysql);
            return FALSE ;
        }
        return TRUE;
	}

	public function query_json($ar, $sql, $condition, $sel_date = "", $hours = "") {
		if (!$this->isConnect())
			return FALSE;
		$where = "";
		$group = "";
		$order = "post_datetime";

		if ($sel_date != '' && $hours != '' && ctype_digit($hours)) {
			$num_mins = $hours * 60 + 30;
			if ($sel_date == 'current')
				$where = "post_datetime >= date_sub(current_timestamp, interval " . $num_mins . " minute) ";	
			else
				$where = "post_datetime >= '" . $sel_date . "' and post_datetime <= date_add('" . $sel_date . "', interval " . $num_mins . " minute) ";
		}

		if ($condition == "interval") {
			if ($sel_date != '' && $hours != '') {
				$num_mins = $hours * 60 + 10;
				if ($sel_date == 'current')
					$where = "post_datetime >= date_sub(current_timestamp, interval " . $num_mins . " minute) ";	
				else
					$where = "post_datetime >= '" . $sel_date . "' and post_datetime <= date_add('" . $sel_date . "', interval " . $num_mins . " minute) ";
			}
			$group = "DATE(post_datetime)";
		} else if ($condition == "day_list" || $condition == "day_rlist") {
			$where = "";
			$group = "DATE(post_datetime)";
			$order .= $condition == "day_rlist" ? " desc" : " asc";
		} else {
			if ($hours == "today")
				$where = "date(post_datetime) > date(current_timestamp - interval 1 day)";
			else if ($sel_date != "" && $hours == "")	// 以降全て
				$where = "date(post_datetime) >= '" . $sel_date . "'";
		}
		$this->sql = $sql;
		if (strlen($where) > 0)
			$this->sql .= " where " . $where;
		if (strlen($group) > 0)
			$this->sql .= " group by " . $group;
		if (strlen($order) > 0)
			$this->sql .= " order by " . $order;

		if (($query_result = mysqli_query($this->mysql, $this->sql)) == FALSE) {
			$this->last_error = mysqli_error($this->mysql);
			return FALSE ;
		}
		$result_count = mysqli_num_rows($query_result);
		$result = array();
		$idx = 0;
		while ($r = mysqli_fetch_assoc($query_result)) {
			$one = array('datetime' => str_replace("-", "/", $r['datetime']));
			if (count($ar) > 0) {
				reset($ar);
				foreach($ar as $val)
					$one += array($val => (float)$r[$val]);
			}
			$result[$idx++] = $one;
		}

		mysqli_free_result($query_result);
		$r = json_encode(array('count' => $result_count, 'data' => $result));
		$json_result = str_replace('\\/', '/', $r);	// before php 5.4
		return $json_result;
	}
}

このクラスは、インスタンスが作成された時点でデータベースenvdataに接続し、デストラクト時に切断する。

public function insert_array($table, $ar)

文字列$tableで指定されるテーブルに、配列$arの内容にしたがってリクエストパラメータの読み取りや初期値の設定を行った後INSERTを行う。$arのキー名は$_GET[]からリクエストパラメータを得るときのパラメータ名とINSERT時のカラム名を兼ねている。所定のパラメータが存在しない場合には、呼び出し時に$arが保持しているキー名に対応する値を設定する。

public function query_json($ar, $sql, $condition, $sel_date, $hours)

テーブルから測定データのクエリを行い、json形式にして返す。このメソッドは、GoogleChartでグラフを描画するためのデータを得るために作成したもの。

$arにはカラム名(または別名)に一致したキー名の配列を与え、$sqlにはテーブル名を含むクエリ用のSQL文を指定する。$conditionはクエリの種別(単純なクエリ、インターバル表示用のクエリ、測定日を得るためのクエリ), $sel_date, $hoursにはクエリ条件を指定する。

以前作ったときはPHP5.5で動かしていたが、今回はPHP5.33を使っているため、json_encode(); のoptions に JSON_UNESCAPED_SLASHES を指定できない。そのため、データ内(この場合は日付)の”/”が”\/” とエスケープされてしまうため、str_replace(‘\\/’, ‘/’, $r); によって除去している。

測定データ格納用スクリプト(store_data.php)

envdata テーブルに測定データを格納するためのPHPスクリプト。測定ボード内のスケッチは、このスクリプトにGETリクエストを送る。

<?PHP
require_once('envdata_db.php');
$ar = array('T' => 0.0, 'P' => 0.0, 'H' => 0.0, 'V' => 0.0, 'X1' => 0.0, 'X2' => 0.0, 'point_id' => 'unknown');

$db = new envdata_db();
if (!$db->insert_array("envdata", $ar)) {
	echo "NG SQL ERROR: " . $envdata->getLast_error() . '<br/>';
	echo $envdata->getLast_sql() . '<br/>';
} else
	echo "OK";
?>

リクエストパラメータ(T, P, H, V, X1, X2)と同じ名前のカラムにデータを格納する。配列$arは、リクエストパラメータのキー名とカラム名、それらに対応した省略値を保持する。

※ リクエストパラメータのpoint_id (現状はMACアドレス)には “:” が含まれているが、特に問題ないのでエスケープしていない。

わざわざarray() を作ったりテーブル名を引数にしているのは、カラム構成や名前の異なるテーブルを複数個作り、測定ボードごとに専用のテーブルを用いるようにしたため。例えばenvdata2テーブルに格納するための store_data2.phpは以下のようになる。

<?PHP
require_once('envdata_db.php');
$ar = array('BMP_T' => 0.0, 'BMP_P' => 0.0, 'HDC_T' => 0.0, 'HDC_H' => 0.0, 'V' => 0.0, 'X1' => 0.0, 'X2' => 0.0, 'point_id' => 'unknown');

$db = new envdata_db();
if (!$db->insert_array("envdata2", $ar)) {
	echo "NG SQL ERROR: " . $envdata->getLast_error() . '<br/>';
	echo $envdata->getLast_sql() . '<br/>';
} else
	echo "OK";
?>

envdata_dbクラスのクエリ関係の使い方やGoogleChartを使った描画用JavaScriptについては、より省電力化を図ったスケッチと共に掲載する予定。